diff --git a/.github/actions/setup-normal-workspace/action.yml b/.github/actions/setup-normal-workspace/action.yml new file mode 100644 index 000000000000..a0781d53c46c --- /dev/null +++ b/.github/actions/setup-normal-workspace/action.yml @@ -0,0 +1,13 @@ +name: 'Setup Java, Gradle and check out the source code' + +runs: + using: composite + steps: + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + cache: gradle + - name: Setup gradle + uses: gradle/actions/setup-gradle@v4 diff --git a/.github/scripts/process_detekt_sarif.sh b/.github/scripts/process_detekt_sarif.sh new file mode 100644 index 000000000000..4d4f2af93298 --- /dev/null +++ b/.github/scripts/process_detekt_sarif.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# This script processes the Detekt SARIF file and outputs results in a format +# suitable for annotation in CI/CD systems. + +SARIF_FILE="$1" + +# Check if SARIF file exists +if [ ! -f "$SARIF_FILE" ]; then + echo "SARIF file not found: $SARIF_FILE" + exit 1 +fi + +# Define jq command to parse SARIF file +read -r -d '' jq_command <<'EOF' +.runs[].results[] | +{ + "full_path": .locations[].physicalLocation.artifactLocation.uri | sub("file://$(pwd)/"; ""), + "file_name": (.locations[].physicalLocation.artifactLocation.uri | split("/") | last), + "l": .locations[].physicalLocation, + "level": .level, + "message": .message.text, + "ruleId": .ruleId +} | +( + "::" + (.level) + + " file=" + (.full_path) + + ",line=" + (.l.region.startLine|tostring) + + ",title=" + (.ruleId) + + ",col=" + (.l.region.startColumn|tostring) + + ",endColumn=" + (.l.region.endColumn|tostring) + + "::" + (.message) +) +EOF + +# Run jq command to format the output +jq -r "$jq_command" < "$SARIF_FILE" diff --git a/.github/workflows/assign-relevant-labels.yml b/.github/workflows/assign-relevant-labels.yml new file mode 100644 index 000000000000..4118c7172093 --- /dev/null +++ b/.github/workflows/assign-relevant-labels.yml @@ -0,0 +1,64 @@ +name: "Assign relevant labels" +on: + pull_request_target: + types: [ opened, edited ] +jobs: + assign-label: + if: github.event.pull_request.state == 'open' + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + contents: read + steps: + - name: label + env: + TITLE: ${{ github.event.pull_request.title }} + LABEL_FIX: Bug Fix + LABEL_BACKEND: Backend + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN}} + script: | + const labelsToAdd = []; + const labelsToRemove = []; + const title = process.env.TITLE.split(":")[0].toUpperCase(); + + if(title.includes("FIX")){ + labelsToAdd.push(process.env.LABEL_FIX); + } else { + labelsToRemove.push(process.env.LABEL_FIX); + } + + if(title.includes("BACKEND")){ + labelsToAdd.push(process.env.LABEL_BACKEND); + } else { + labelsToRemove.push(process.env.LABEL_BACKEND); + } + + for (const label of labelsToAdd) { + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [label] + }); + } + + const {data} = await github.rest.issues.listLabelsOnIssue({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + + for (const label of labelsToRemove) { + const filtered = data.filter(l => l.name == label); + if(filtered.length == 1){ + github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: label + }); + } + } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8275ac8b0740..c8a1303921e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,43 +18,50 @@ jobs: runs-on: ubuntu-latest name: "Build and test" steps: - - uses: actions/checkout@v3 - - name: Set up JDK 21 - uses: actions/setup-java@v3 - with: - java-version: 21 - distribution: temurin - cache: gradle - - name: Setup gradle - uses: gradle/gradle-build-action@v2 + - name: Checkout code + uses: actions/checkout@v4 + - uses: ./.github/actions/setup-normal-workspace - name: Build with Gradle run: ./gradlew assemble -x test --stacktrace - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload development build with: name: "Development Build" - path: versions/1.8.9/build/libs/*.jar + path: build/libs/*.jar - name: Test with Gradle run: ./gradlew test - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: "Upload test report" if: ${{ !cancelled() }} with: name: "Test Results" path: versions/1.8.9/build/reports/tests/test/ + detekt: + name: Run detekt + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + - uses: ./.github/actions/setup-normal-workspace + # detektMain is a LOT slower than detekt, but it does type analysis + - name: Run detekt main (w/typing analysis) + run: | + ./gradlew detektMain --stacktrace + - name: Annotate detekt failures + if: ${{ !cancelled() }} + run: | + chmod +x .github/scripts/process_detekt_sarif.sh + ./.github/scripts/process_detekt_sarif.sh versions/1.8.9/build/reports/detekt/main.sarif + + preprocess: runs-on: ubuntu-latest name: "Build multi version" steps: - - uses: actions/checkout@v3 - - name: Set up JDK 21 - uses: actions/setup-java@v3 - with: - java-version: 21 - distribution: temurin - cache: gradle - - name: Setup gradle - uses: gradle/gradle-build-action@v2 + - name: Checkout code + uses: actions/checkout@v4 + - uses: ./.github/actions/setup-normal-workspace - name: Enable preprocessor run: | mkdir -p .gradle diff --git a/.github/workflows/check-style.yaml.disabled b/.github/workflows/check-style.yaml.disabled deleted file mode 100644 index ff172208f8bc..000000000000 --- a/.github/workflows/check-style.yaml.disabled +++ /dev/null @@ -1,16 +0,0 @@ -name: check-style -on: - - pull_request -jobs: - ktlint: - name: Check Style - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - name: Checkout code - - name: ktlint - uses: ScaCap/action-ktlint@master - with: - github_token: ${{ secrets.github_token }} - reporter: github-pr-check diff --git a/.github/workflows/generate-constants.yaml b/.github/workflows/generate-constants.yaml index c589676d0e2f..f6fab6f243ac 100644 --- a/.github/workflows/generate-constants.yaml +++ b/.github/workflows/generate-constants.yaml @@ -29,7 +29,7 @@ jobs: - name: Generate Repo Patterns using Gradle run: | ./gradlew generateRepoPatterns --stacktrace - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload generated repo regexes with: name: Repo Regexes @@ -45,7 +45,7 @@ jobs: with: repository: ${{ env.data_repo }} branch: main - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 name: Upload generated repo regexes with: name: Repo Regexes diff --git a/.github/workflows/illegal-imports.txt b/.github/workflows/illegal-imports.txt index f53a5da78e25..83354f622960 100644 --- a/.github/workflows/illegal-imports.txt +++ b/.github/workflows/illegal-imports.txt @@ -5,7 +5,8 @@ at/hannibal2/skyhanni/ scala. at/hannibal2/skyhanni/ jline. -at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.util.Constants at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.events.SlotClickEvent at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.events.ReplaceItemEvent +at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.util.Constants +at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.util.Utils at/hannibal2/skyhanni/ java.util.function.Supplier diff --git a/.github/workflows/label-bug-fix.yml b/.github/workflows/label-bug-fix.yml deleted file mode 100644 index dae2980de630..000000000000 --- a/.github/workflows/label-bug-fix.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: "Bug Fix label" -on: - pull_request_target: - types: [ opened, edited ] -jobs: - assign-label: - if: github.event.pull_request.state == 'open' # Condition to check if PR is open - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - contents: read - steps: - - name: label - env: - TITLE: ${{ github.event.pull_request.title }} - LABEL: Bug Fix - Sooner than Very Soon - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN}} - script: | - if(process.env.TITLE.split(":")[0].toUpperCase().includes("FIX")){ - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [process.env.LABEL] - }) - }else{ - const {data} = await github.rest.issues.listLabelsOnIssue({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }) - const filtered = data.filter(label => label.name == process.env.LABEL) - if(filtered.length == 1){ - github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: process.env.LABEL - }) - } - } diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 000000000000..3daad12374c5 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,57 @@ +name: "PR Changelog Verification" + +on: + pull_request_target: + types: [ opened, edited, ready_for_review ] + +jobs: + verify-changelog: + if: github.event.pull_request.state == 'open' && '511310721' == github.repository_id && github.event.pull_request.draft == false + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-normal-workspace + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Run ChangeLog verification + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + run: | + ./gradlew checkPrDescription -PprTitle="${PR_TITLE}" -PprBody="${PR_BODY}" + + - name: Add label if changelog verification fails + if: failure() + uses: actions-ecosystem/action-add-labels@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: 'Wrong Title/Changelog' + + - name: Remove label if changelog verification passes + if: success() + uses: actions-ecosystem/action-remove-labels@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: 'Wrong Title/Changelog' + + - name: Add comment to PR if changelog verification fails + if: failure() + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const test = fs.readFileSync('versions/1.8.9/build/changelog_errors.txt', 'utf8'); + const commentBody = `${test}` + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }) diff --git a/.gitignore b/.gitignore index d77f4bd8b6f0..7f4081e02e52 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ !.idea/icon.svg !.idea/dictionaries/default_user.xml !.idea/scopes/Mixins.xml +!.idea/liveTemplates/SkyHanni.xml .vscode/ run/ build/ diff --git a/.idea/dictionaries/default_user.xml b/.idea/dictionaries/default_user.xml index b166e60d38fd..fc3066750657 100644 --- a/.idea/dictionaries/default_user.xml +++ b/.idea/dictionaries/default_user.xml @@ -63,6 +63,7 @@ dicer disintegrator disintegrators + draconic dragontail dreadfarm dreadlord @@ -81,10 +82,12 @@ explosivity ezpz fairylosopher + fels fermento firedust firesale firesales + flowstate framebuffer getfromsacks ghast @@ -100,6 +103,7 @@ hideparticles hoppity hoppity's + hoppitys horsezooka hotbar hotm @@ -145,7 +149,9 @@ millenia minecart mineman + mineshafts miniboss + minigame mirrorverse misclick missclick @@ -190,6 +196,8 @@ pling pocalypse polarvoid + powerup + powerups preinitialization procs prospection @@ -248,9 +256,11 @@ supercraft supercrafting superlite + superpair superpairs tablist terracottas + tessellator thaumaturgist thaumaturgy townsquare @@ -274,4 +284,4 @@ yolkar - + \ No newline at end of file diff --git a/.idea/liveTemplates/SkyHanni.xml b/.idea/liveTemplates/SkyHanni.xml new file mode 100644 index 000000000000..08c6737b5ce2 --- /dev/null +++ b/.idea/liveTemplates/SkyHanni.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.live-plugins/module/plugin.kts b/.live-plugins/module/plugin.kts index c0a65d3f431b..f7471e9fd98c 100644 --- a/.live-plugins/module/plugin.kts +++ b/.live-plugins/module/plugin.kts @@ -18,6 +18,7 @@ val forgeEvent = "SubscribeEvent" val handleEvent = "HandleEvent" val skyHanniModule = "SkyHanniModule" +val skyhanniPath = "at.hannibal2.skyhanni" val patternGroup = "at.hannibal2.skyhanni.utils.repopatterns.RepoPatternGroup" val pattern = "java.util.regex.Pattern" @@ -36,12 +37,17 @@ fun isRepoPattern(property: KtProperty): Boolean { return false } +fun isFromSkyhanni(declaration: KtNamedDeclaration): Boolean { + return declaration.fqName?.asString()?.startsWith(skyhanniPath) ?: false +} + class ModuleInspectionKotlin : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { val visitor = object : KtVisitorVoid() { override fun visitClass(klass: KtClass) { + if (!isFromSkyhanni(klass)) return val hasAnnotation = klass.annotationEntries.any { it.shortName?.asString() == skyHanniModule } if (hasAnnotation) { @@ -54,6 +60,7 @@ class ModuleInspectionKotlin : AbstractKotlinInspection() { } override fun visitObjectDeclaration(declaration: KtObjectDeclaration) { + if (!isFromSkyhanni(declaration)) return val hasAnnotation = declaration.annotationEntries.any { it.shortName?.asString() == skyHanniModule } if (hasAnnotation) return diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57f22f2e2583..8229a00d5835 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ We use [IntelliJ](https://www.jetbrains.com/idea/) as an example. - Download IntelliJ from the [JetBrains Website](https://www.jetbrains.com/idea/download/). - Use the Community Edition. (Scroll down a bit.) +- When you encounter any bug with IntelliJ, please make sure to use the version `2024.1.6`, not `2024.2.x` or above. ### Cloning the project @@ -30,14 +31,66 @@ We use [IntelliJ](https://www.jetbrains.com/idea/) as an example. ### Setting up IntelliJ -SkyHanni's Gradle configuration is very similar to the one used in **NotEnoughUpdates**, just -follow [their guide](https://github.com/NotEnoughUpdates/NotEnoughUpdates/blob/master/CONTRIBUTING.md). + +Once your project is imported into IntelliJ from the previous step, all dependencies like Minecraft, NEU, and so on should be automatically +downloaded. If not, you might need to link the Gradle project in the Gradle tab (little elephant) on the right. + +
+🖼️Show Gradle tab image + +![Gradle tab with Link Project and Gradle Settings highlighted](docs/gradle-tab.jpg) + +
+ +If importing fails, make sure the Gradle JVM (found in the settings wheel in the Gradle tab, or by searching Ctrl + Shift + A +for "Gradle JVM") is set to a Java 21 JDK. While this is not the version of Java Minecraft 1.8.9 uses, we need this version for some of our +build tools. + +
+🖼️Show Gradle JVM image + +![Gradle settings showing Java 21 being selected as JVM](docs/gradle-settings.png) + +
+ +After all importing is done (which might take a few minutes the first time you download the project), you should find a new IntelliJ run +configuration. + +
+🖼️Show run configuration selection image + +![Where to select the run configuration](docs/minecraft-client.webp) + +
+ +That task might work out of the box, but very likely it will not. We will need to adjust the used Java version. Since Minecraft 1.8.9 uses +Java 1.8, we will need to adjust the used JDK for running our Mod, as well as potentially changing the argument passing style. + +So select an appropriate Java 1.8 JDK (preferably [DCEVM](#hot-swap), but any Java 1.8 JDK or even JRE will do) and select None as the +argument passing style. + +
+🖼️Show run configuration image + +![Run configuration settings](docs/run-configuration-settings.avif) + +
+ +Now that we are done with that, you should be able to launch your game from your IDE with that run configuration. + +SkyHanni's Gradle configuration is very similar to the one used in **NotEnoughUpdates**, so if you want to look at another guide, check +out [their guide](https://github.com/NotEnoughUpdates/NotEnoughUpdates/blob/master/CONTRIBUTING.md). ## Creating a Pull Request If you are not very familiar with git, you might want to try this out: https://learngitbranching.js.org/. -_An explanation how to use intellij and branches will follow here soon._ +Proposed changes are better off being in their own branch, you can do this by doing the following from within IntelliJ with the SkyHanni project already open. +- Click the beta dropdown at the top of IntelliJ +- Click new branch +- Give the branch a name relating to the changes you plan to make + +_A more in depth explanation how to use intellij and branches will follow here soon._ Please use a prefix for the name of the PR (E.g. Feature, Improvement, Fix, Backend, ...). @@ -51,6 +104,13 @@ format like "- #821" to illustrate the dependency. - Follow the [Hypixel Rules](https://hypixel.net/rules). - Use the coding conventions for [Kotlin](https://kotlinlang.org/docs/coding-conventions.html) and [Java](https://www.oracle.com/java/technologies/javase/codeconventions-contents.html). +- **My build is failing due to `detekt`, what do I do?** + - `detekt` is our code quality tool. It checks for code smells and style issues. + - If you have a build failure stating `Analysis failed with ... weighted issues.`, you can check `versions/[target version]/build/reports/detekt/` for a comprehensive list of issues. + - **There are valid reasons to deviate from the norm** + - If you have such a case, either use `@Supress("rule_name")`, or re-build the `baseline.xml` file, using `./gradlew detektBaselineMain`. + After running detektBaselineMain, you should find a file called `baseline-main.xml` in the `version/1.8.9` folder, rename the file to + `baseline.xml` replacing the old one. You also should copy the new contents of this file to the [main baseline file](detekt/baseline.xml) - Do not copy features from other mods. Exceptions: - Mods that are paid to use. - Mods that have reached their end of life. (Rip SBA, Dulkir and Soopy). diff --git a/README.md b/README.md index a7c00b495c57..3c7721cfb176 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,19 @@ ## What it does -SkyHanni is a Forge mod for Minecraft 1.8.9 that adds many useful features to Hypixel SkyBlock. With SkyHanni, you'll get: +SkyHanni is a Forge mod for Minecraft 1.8.9 that adds many useful features to [Hypixel SkyBlock](https://wiki.hypixel.net/Main_Page). With SkyHanni you have access to: -* **Helpful GUIs:** Access important information at a glance. -* **Extra Chat Messages:** Receive reminders and helpful tips. -* **Message Hiders:** Control which messages you see in chat. -* **Entity/Item Highlighters:** Focus on important mobs or items in the world/your inventory. -* **[And much more!](docs/FEATURES.md)** +* **Helpful GUIs:** View important information at a glance. +* **Extra Chat Messages:** Receive reminders and tips at the right moment. +* **Object Highlighters:** Focus on important items in inventories or highlight mobs in the world. +* **Highly Customizable Displays:** Personalise your Scoreboard, Tab List or chat format. +* [And **much** more!](docs/FEATURES.md) -SkyHanni is especially helpful when doing activities like farming, slayers, Bingo, Diana, fishing, or Rift. +SkyHanni is especially useful when doing farming, slayers, Bingo, Diana, fishing, Rift or mining. ## Getting Started -1. **Install:** Check out the [installation guide](docs/INSTALLING.md). +1. **Install:** Follow the [installation guide](docs/INSTALLING.md). 2. **Set Up:** Type `/sh` or `/skyhanni` in-game to configure your settings. 3. **Explore:** See all the features [here](docs/FEATURES.md). @@ -34,17 +34,17 @@ SkyHanni is especially helpful when doing activities like farming, slayers, Bing Give feedback or just chat with others on our community Discord! -* **Bug Reports:** Use the `#bug-reports` channel. -* **Feature Suggestions:** Use the `#suggestions` channel. -* **General Chat:** Chat with other SkyHanni users in `#skyblock-general` channel. +* **Bug Reports:** Use the `#bug-reports` channel when you find broken features (please check out `#faq` and `#known-bugs`). +* **Quick Help** Ask in `#support` for questions and problems with the mod or Minecraft in general. +* **Feature Suggestions:** Feel fre to tell your ideas in `#suggestions` channel for new features and improvements to the mod. (Don't copy from existing mods or break Hypixel rules). +* **General Chat:** Chat with other SkyHanni users in `#skyblock-general` channel about the game. -[Join the Discord](https://discord.gg/skyhanni-997079228510117908) +**[Join the Discord!](https://discord.gg/skyhanni-997079228510117908)** ## Contributing -Interested in writing your own SkyHanni feature or fixing that one annoying bug yourself? Check out our [contributing guide](CONTRIBUTING.md) for more information. +Are you interested in writing your own SkyHanni feature? Do you want to fix that one annoying bug yourself? Check out our [contributing guide](CONTRIBUTING.md) for more information! --- -**SkyHanni is part of an active modding community. Explore other useful mods [here](https://sbmw.ca/mod-lists/skyblock-mod-list/) to -complete your SkyBlock setup!** +**SkyHanni is part of an active modding community. Explore other useful mods [here](https://sbmw.ca/mod-lists/skyblock-mod-list/) for even more SkyBlock features!** diff --git a/build.gradle.kts b/build.gradle.kts index e8464ada27e8..cddef09fd3ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,9 +3,13 @@ import at.skyhanni.sharedvariables.MultiVersionStage import at.skyhanni.sharedvariables.ProjectTarget import at.skyhanni.sharedvariables.SHVersionInfo import at.skyhanni.sharedvariables.versionString +import io.gitlab.arturbosch.detekt.Detekt +import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask import net.fabricmc.loom.task.RunGameTask import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import skyhannibuildsystem.ChangelogVerification +import skyhannibuildsystem.DownloadBackupRepo plugins { idea @@ -18,31 +22,12 @@ plugins { kotlin("plugin.power-assert") `maven-publish` id("moe.nea.shot") version "1.0.0" + id("io.gitlab.arturbosch.detekt") + id("net.kyori.blossom") } val target = ProjectTarget.values().find { it.projectPath == project.path }!! -repositories { - mavenCentral() - mavenLocal() - maven("https://maven.minecraftforge.net") { - metadataSources { - artifact() // We love missing POMs - } - } - maven("https://repo.spongepowered.org/maven/") // mixin - maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") // DevAuth - maven("https://jitpack.io") { // NotEnoughUpdates (compiled against) - content { - includeGroupByRegex("(com|io)\\.github\\..*") - } - } - maven("https://repo.nea.moe/releases") // libautoupdate - maven("https://maven.notenoughupdates.org/releases") // NotEnoughUpdates (dev env) - maven("https://repo.hypixel.net/repository/Hypixel/") // mod-api - maven("https://maven.teamresourceful.com/repository/thatgravyboat/") // DiscordIPC -} - // Toolchains: java { toolchain.languageVersion.set(target.minecraftVersion.javaLanguageVersion) @@ -56,11 +41,15 @@ val runDirectory = rootProject.file("run") runDirectory.mkdirs() // Minecraft configuration: loom { - if (this.isForgeLike) + if (this.isForgeLike) { forge { - pack200Provider.set(dev.architectury.pack200.java.Pack200Adapter()) + pack200Provider.set( + dev.architectury.pack200.java + .Pack200Adapter(), + ) mixinConfig("mixins.skyhanni.json") } + } mixin { useLegacyMixinAp.set(true) defaultRefmapName.set("mixins.skyhanni.refmap.json") @@ -84,11 +73,12 @@ loom { } } -if (target == ProjectTarget.MAIN) +if (target == ProjectTarget.MAIN) { sourceSets.main { resources.destinationDirectory.set(kotlin.destinationDirectory) output.setResourcesDir(kotlin.destinationDirectory) } +} val shadowImpl: Configuration by configurations.creating { configurations.implementation.get().extendsFrom(this) @@ -107,11 +97,26 @@ val headlessLwjgl by configurations.creating { isTransitive = false isVisible = false } + +val includeBackupRepo by tasks.registering(DownloadBackupRepo::class) { + this.outputDirectory.set(layout.buildDirectory.dir("downloadedRepo")) + this.branch = "main" +} + tasks.runClient { - this.javaLauncher.set(javaToolchains.launcherFor { - languageVersion.set(target.minecraftVersion.javaLanguageVersion) - }) + this.javaLauncher.set( + javaToolchains.launcherFor { + languageVersion.set(target.minecraftVersion.javaLanguageVersion) + }, + ) +} + +tasks.register("checkPrDescription", ChangelogVerification::class) { + this.outputDirectory.set(layout.buildDirectory) + this.prTitle = project.findProperty("prTitle") as String + this.prBody = project.findProperty("prBody") as String } + val shot = shots.shot("minecraft", rootProject.file("shots.txt")) dependencies { @@ -121,8 +126,9 @@ dependencies { } else { mappings(target.mappingDependency) } - if (target.forgeDep != null) + if (target.forgeDep != null) { "forge"(target.forgeDep!!) + } // Discord RPC client shadowImpl("com.jagrosh:DiscordIPC:0.5.3") { @@ -146,6 +152,9 @@ dependencies { annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT") annotationProcessor("com.google.code.gson:gson:2.10.1") annotationProcessor("com.google.guava:guava:17.0") + } else if (target == ProjectTarget.MODERN) { + modCompileOnly("net.fabricmc:fabric-loader:0.16.7") + modCompileOnly("net.fabricmc.fabric-api:fabric-api:0.102.0+1.21") } implementation(kotlin("stdlib-jdk8")) @@ -153,15 +162,16 @@ dependencies { exclude(group = "org.jetbrains.kotlin") } - modRuntimeOnly("me.djtheredstoner:DevAuth-forge-legacy:1.1.0") + if (target.isForge) modRuntimeOnly("me.djtheredstoner:DevAuth-forge-legacy:1.2.1") + else modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.1") modCompileOnly("com.github.hannibal002:notenoughupdates:4957f0b:all") { exclude(module = "unspecified") isTransitive = false } - // June 3, 2024, 9:30 PM AEST - // https://github.com/NotEnoughUpdates/NotEnoughUpdates/tree/2.3.0 - devenvMod("com.github.NotEnoughUpdates:NotEnoughUpdates:2.3.0:all") { + // October 3, 2024, 11:43 PM AEST + // https://github.com/NotEnoughUpdates/NotEnoughUpdates/tree/2.4.0 + devenvMod("com.github.NotEnoughUpdates:NotEnoughUpdates:2.4.0:all") { exclude(module = "unspecified") isTransitive = false } @@ -177,10 +187,17 @@ dependencies { exclude(module = "unspecified") isTransitive = false } - testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("org.junit.jupiter:junit-jupiter:5.11.0") testImplementation("io.mockk:mockk:1.12.5") implementation("net.hypixel:mod-api:0.3.1") + + // getting clock offset + shadowImpl("commons-net:commons-net:3.8.0") + + detektPlugins("org.notenoughupdates:detektrules:1.0.0") + detektPlugins(project(":detekt")) + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.7") } afterEvaluate { @@ -207,6 +224,7 @@ kotlin { // Tasks: tasks.processResources { + from(includeBackupRepo) inputs.property("version", version) filesMatching(listOf("mcmod.info", "fabric.mod.json")) { expand("version" to version) @@ -231,10 +249,15 @@ if (target == ProjectTarget.MAIN) { } } -if (target == ProjectTarget.MAIN) +if (target == ProjectTarget.MAIN) { tasks.compileJava { dependsOn(tasks.processResources) } +} + +tasks.withType { + compilerOptions.jvmTarget.set(JvmTarget.fromTarget(target.minecraftVersion.formattedJavaLanguageVersion)) +} if (target.parent == ProjectTarget.MAIN) { val mainRes = project(ProjectTarget.MAIN.projectPath).tasks.getAt("processResources") @@ -285,6 +308,7 @@ tasks.shadowJar { relocate("io.github.notenoughupdates.moulconfig", "at.hannibal2.skyhanni.deps.moulconfig") relocate("moe.nea.libautoupdate", "at.hannibal2.skyhanni.deps.libautoupdate") relocate("com.jagrosh.discordipc", "at.hannibal2.skyhanni.deps.discordipc") + relocate("org.apache.commons.net", "at.hannibal2.skyhanni.deps.commons.net") } tasks.jar { archiveClassifier.set("nodeps") @@ -312,12 +336,19 @@ if (!MultiVersionStage.activeState.shouldCompile(target)) { onlyIf { false } } } + preprocess { vars.put("MC", target.minecraftVersion.versionNumber) - vars.put("FORGE", if (target.forgeDep != null) 1 else 0) + vars.put("FORGE", if (target.isForge) 1 else 0) + vars.put("FABRIC", if (target.isFabric) 1 else 0) vars.put("JAVA", target.minecraftVersion.javaVersion) patternAnnotation.set("at.hannibal2.skyhanni.utils.compat.Pattern") } + +blossom { + replaceToken("@MOD_VERSION@", version) +} + val sourcesJar by tasks.creating(Jar::class) { destinationDirectory.set(layout.buildDirectory.dir("badjars")) archiveClassifier.set("src") @@ -343,3 +374,32 @@ publishing.publications { } } } + +detekt { + buildUponDefaultConfig = true // preconfigure defaults + config.setFrom(rootProject.layout.projectDirectory.file("detekt/detekt.yml")) // point to your custom config defining rules to run, overwriting default behavior + baseline = file(layout.projectDirectory.file("detekt/baseline.xml")) // a way of suppressing issues before introducing detekt + source.setFrom(project.sourceSets.named("main").map { it.allSource }) +} + +tasks.withType().configureEach { + onlyIf { + target == ProjectTarget.MAIN + } + + reports { + html.required.set(true) // observe findings in your browser with structure and code snippets + xml.required.set(true) // checkstyle like format mainly for integrations like Jenkins + sarif.required.set(true) // standardized SARIF format (https://sarifweb.azurewebsites.net/) to support integrations with GitHub Code Scanning + md.required.set(true) // simple Markdown format + } +} + +tasks.withType().configureEach { + jvmTarget = target.minecraftVersion.formattedJavaLanguageVersion + outputs.cacheIf { false } // Custom rules won't work if cached +} +tasks.withType().configureEach { + jvmTarget = target.minecraftVersion.formattedJavaLanguageVersion + outputs.cacheIf { false } // Custom rules won't work if cached +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000000..505f438b98c2 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + maven("https://jitpack.io") { + content { + includeGroupByRegex("com\\.github\\..*") + } + } +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib") + implementation("com.github.SkyHanniStudios:SkyHanniChangelogBuilder:1.0.1") +} diff --git a/buildSrc/src/main/kotlin/skyhannibuildsystem/ChangelogVerification.kt b/buildSrc/src/main/kotlin/skyhannibuildsystem/ChangelogVerification.kt new file mode 100644 index 000000000000..8ac54d6fc0b1 --- /dev/null +++ b/buildSrc/src/main/kotlin/skyhannibuildsystem/ChangelogVerification.kt @@ -0,0 +1,64 @@ +package skyhannibuildsystem + +import at.hannibal2.changelog.SkyHanniChangelogBuilder +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import java.io.File + +abstract class ChangelogVerification : DefaultTask() { + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @Input + var prTitle: String = "" + + @Input + var prBody: String = "" + + @get:Internal + val prBodyLines get() = prBody.lines() + + private val prLink = "ignored" + private val templateLocation = "https://github.com/hannibal002/SkyHanni/blob/beta/pull_request_template.md" + + @TaskAction + fun scanChangelog() { + if (prBodyLines.contains("exclude_from_changelog")) { + println("PR is excluded from changelog verification") + return + } + + val (changes, bodyErrors) = SkyHanniChangelogBuilder.findChanges(prBodyLines, prLink) + val titleErrors = SkyHanniChangelogBuilder.findPullRequestNameErrors(prTitle, changes) + + if (bodyErrors.isEmpty() && titleErrors.isEmpty()) { + println("Changelog and title verification successful") + } else { + bodyErrors.forEach { println(it.message) } + titleErrors.forEach { println(it.message) } + + // Export errors so that they can be listed in the PR comment + val errorFile = File(outputDirectory.get().asFile, "changelog_errors.txt") + println("saved error file to: ${errorFile.path}") + + errorFile.appendText("I have detected some issues with your pull request:\n\n") + + if (bodyErrors.isNotEmpty()) { + errorFile.appendText("Body issues:\n${bodyErrors.joinToString("\n") { it.formatLine() }}\n\n") + } + if (titleErrors.isNotEmpty()) { + errorFile.appendText("Title issues:\n${titleErrors.joinToString("\n") { it.message }}\n\n") + } + + errorFile.appendText("Please fix these issues. For the correct format, refer to the [pull request template]($templateLocation).") + + throw GradleException("Changelog verification failed") + } + } +} diff --git a/buildSrc/src/main/kotlin/skyhannibuildsystem/DownloadBackupRepo.kt b/buildSrc/src/main/kotlin/skyhannibuildsystem/DownloadBackupRepo.kt new file mode 100644 index 000000000000..f59bf34a8c10 --- /dev/null +++ b/buildSrc/src/main/kotlin/skyhannibuildsystem/DownloadBackupRepo.kt @@ -0,0 +1,34 @@ +package skyhannibuildsystem + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import java.net.URL + +// Code taken from NotEnoughUpdates +abstract class DownloadBackupRepo : DefaultTask() { + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @get:Input + abstract var branch: String + + @get:Internal + val repoFile get() = outputDirectory.get().asFile.resolve("assets/skyhanni/repo.zip") + + @TaskAction + fun downloadRepo() { + val downloadUrl = URL("https://github.com/hannibal002/SkyHanni-Repo/archive/refs/heads/$branch.zip") + val file = repoFile + file.parentFile.mkdirs() + file.outputStream().use { out -> + downloadUrl.openStream().use { inp -> + inp.copyTo(out) + } + } + } +} diff --git a/detekt/baseline.xml b/detekt/baseline.xml new file mode 100644 index 000000000000..338c7d66d3cf --- /dev/null +++ b/detekt/baseline.xml @@ -0,0 +1,314 @@ + + + + + ArrayPrimitive:CropMoneyDisplay.kt$CropMoneyDisplay$Array<Double> + ArrayPrimitive:CropMoneyDisplay.kt$CropMoneyDisplay$arrayOf(npcPrice) + ArrayPrimitive:CropMoneyDisplay.kt$CropMoneyDisplay$arrayOf(sellOffer) + ArrayPrimitive:LorenzVec.kt$Array<Double> + ArrayPrimitive:LorenzVec.kt$LorenzVec$Array<Double> + ArrayPrimitive:LorenzVec.kt$LorenzVec$Array<Float> + ArrayPrimitive:LorenzVec.kt$LorenzVec$arrayOf(x, y, z) + ArrayPrimitive:LorenzVec.kt$LorenzVec$arrayOf(x.toFloat(), y.toFloat(), z.toFloat()) + CyclomaticComplexMethod:AdvancedPlayerList.kt$AdvancedPlayerList$fun newSorting(original: List<String>): List<String> + CyclomaticComplexMethod:CropMoneyDisplay.kt$CropMoneyDisplay$private fun calculateMoneyPerHour(debugList: MutableList<List<Any>>): Map<NEUInternalName, Array<Double>> + CyclomaticComplexMethod:CropMoneyDisplay.kt$CropMoneyDisplay$private fun drawDisplay(): List<List<Any>> + CyclomaticComplexMethod:DamageIndicatorManager.kt$DamageIndicatorManager$private fun checkThorn(realHealth: Long, realMaxHealth: Long): String? + CyclomaticComplexMethod:EstimatedItemValueCalculator.kt$EstimatedItemValueCalculator$private fun addEnchantments(stack: ItemStack, list: MutableList<String>): Double + CyclomaticComplexMethod:GardenBestCropTime.kt$GardenBestCropTime$fun drawBestDisplay(currentCrop: CropType?): List<List<Any>> + CyclomaticComplexMethod:GardenCropMilestoneDisplay.kt$GardenCropMilestoneDisplay$private fun drawProgressDisplay(crop: CropType): List<Renderable> + CyclomaticComplexMethod:GardenVisitorFeatures.kt$GardenVisitorFeatures$private fun readToolTip(visitor: VisitorAPI.Visitor, itemStack: ItemStack?, toolTip: MutableList<String>) + CyclomaticComplexMethod:GhostCounter.kt$GhostCounter$private fun drawDisplay() + CyclomaticComplexMethod:GraphEditor.kt$GraphEditor$private fun input() + CyclomaticComplexMethod:ItemDisplayOverlayFeatures.kt$ItemDisplayOverlayFeatures$private fun getStackTip(item: ItemStack): String? + CyclomaticComplexMethod:ItemNameResolver.kt$ItemNameResolver$internal fun getInternalNameOrNull(itemName: String): NEUInternalName? + CyclomaticComplexMethod:MinecraftConsoleFilter.kt$MinecraftConsoleFilter$override fun filter(event: LogEvent?): Filter.Result + CyclomaticComplexMethod:PacketTest.kt$PacketTest$private fun Packet<*>.print() + CyclomaticComplexMethod:ParkourHelper.kt$ParkourHelper$fun render(event: LorenzRenderWorldEvent) + CyclomaticComplexMethod:Renderable.kt$Renderable.Companion$internal fun shouldAllowLink(debug: Boolean = false, bypassChecks: Boolean): Boolean + CyclomaticComplexMethod:SkillProgress.kt$SkillProgress$private fun drawDisplay() + CyclomaticComplexMethod:VampireSlayerFeatures.kt$VampireSlayerFeatures$private fun EntityOtherPlayerMP.process() + CyclomaticComplexMethod:VisualWordGui.kt$VisualWordGui$override fun drawScreen(unusedX: Int, unusedY: Int, partialTicks: Float) + Filename:AreaChangeEvents.kt$at.hannibal2.skyhanni.events.skyblock.AreaChangeEvents.kt + InjectDispatcher:ClipboardUtils.kt$ClipboardUtils$IO + InjectDispatcher:GardenNextJacobContest.kt$GardenNextJacobContest$IO + InjectDispatcher:HypixelBazaarFetcher.kt$HypixelBazaarFetcher$IO + InjectDispatcher:MayorAPI.kt$MayorAPI$IO + LongMethod:CopyNearbyEntitiesCommand.kt$CopyNearbyEntitiesCommand$fun command(args: Array<String>) + LongMethod:CropMoneyDisplay.kt$CropMoneyDisplay$private fun drawDisplay(): List<List<Any>> + LongMethod:GhostCounter.kt$GhostCounter$private fun drawDisplay() + LongMethod:GraphEditor.kt$GraphEditor$private fun input() + LongMethod:ItemDisplayOverlayFeatures.kt$ItemDisplayOverlayFeatures$private fun getStackTip(item: ItemStack): String? + LongMethod:MinecraftConsoleFilter.kt$MinecraftConsoleFilter$override fun filter(event: LogEvent?): Filter.Result + LongMethod:RenderableTooltips.kt$RenderableTooltips$private fun drawHoveringText() + LongMethod:TabListRenderer.kt$TabListRenderer$private fun drawTabList() + LongMethod:VisualWordGui.kt$VisualWordGui$override fun drawScreen(unusedX: Int, unusedY: Int, partialTicks: Float) + LoopWithTooManyJumpStatements:AdvancedPlayerList.kt$AdvancedPlayerList$for + LoopWithTooManyJumpStatements:CropMoneyDisplay.kt$CropMoneyDisplay$for + LoopWithTooManyJumpStatements:CustomScoreboard.kt$CustomScoreboard$for + LoopWithTooManyJumpStatements:EstimatedItemValueCalculator.kt$EstimatedItemValueCalculator$for + LoopWithTooManyJumpStatements:GardenComposterInventoryFeatures.kt$GardenComposterInventoryFeatures$for + LoopWithTooManyJumpStatements:GardenVisitorFeatures.kt$GardenVisitorFeatures$for + LoopWithTooManyJumpStatements:IslandAreas.kt$IslandAreas$for + LoopWithTooManyJumpStatements:RiftBloodEffigies.kt$RiftBloodEffigies$for + LoopWithTooManyJumpStatements:SkyBlockItemModifierUtils.kt$SkyBlockItemModifierUtils$for + LoopWithTooManyJumpStatements:SkyHanniConfigSearchResetCommand.kt$SkyHanniConfigSearchResetCommand$for + LoopWithTooManyJumpStatements:SuperpairsClicksAlert.kt$SuperpairsClicksAlert$for + MapGetWithNotNullAssertionOperator:NavigationHelper.kt$NavigationHelper$distances[node]!! + MatchingDeclarationName:AreaChangeEvents.kt$ScoreboardAreaChangeEvent : SkyHanniEvent + MemberNameEqualsClassName:CaptureFarmingGear.kt$CaptureFarmingGear$fun captureFarmingGear() + MemberNameEqualsClassName:FameRanks.kt$FameRanks$var fameRanks = emptyMap<String, FameRank>() private set + MemberNameEqualsClassName:FirstMinionTier.kt$FirstMinionTier$fun firstMinionTier( otherItems: Map<NEUInternalName, Int>, minions: MutableMap<String, NEUInternalName>, tierOneMinions: MutableList<NEUInternalName>, tierOneMinionsDone: MutableSet<NEUInternalName>, ) + MemberNameEqualsClassName:LastServers.kt$LastServers$private val lastServers = mutableMapOf<String, SimpleTimeMark>() + MemberNameEqualsClassName:PestSpawn.kt$PestSpawn$private fun pestSpawn(amount: Int, plotNames: List<String>, unknownAmount: Boolean) + MemberNameEqualsClassName:Shimmy.kt$Shimmy.Companion$private fun shimmy(source: Any?, fieldName: String): Any? + MemberNameEqualsClassName:TestBingo.kt$TestBingo$var testBingo = false + MemberNameEqualsClassName:Text.kt$Text$fun text(text: String, init: IChatComponent.() -> Unit = {}) + NoNameShadowing:BucketedItemTrackerData.kt$BucketedItemTrackerData${ it.hidden = !it.hidden } + NoNameShadowing:BurrowWarpHelper.kt$BurrowWarpHelper${ it.startsWith("§bWarp to ") } + NoNameShadowing:ChunkedStat.kt$ChunkedStat.Companion${ it.showWhen() } + NoNameShadowing:ContributorManager.kt$ContributorManager${ it.isAllowed() } + NoNameShadowing:Graph.kt$Graph.Companion${ out.name("Name").value(it) } + NoNameShadowing:Graph.kt$Graph.Companion${ out.name("Tags") out.beginArray() for (tagName in it) { out.value(tagName) } out.endArray() } + NoNameShadowing:GraphEditorBugFinder.kt$GraphEditorBugFinder${ it.position.distanceSqToPlayer() } + NoNameShadowing:GuiOptionEditorUpdateCheck.kt$GuiOptionEditorUpdateCheck$width + NoNameShadowing:HoppityCollectionStats.kt$HoppityCollectionStats${ val displayAmount = it.amount.shortFormat() val operationFormat = when (milestoneType) { HoppityEggType.CHOCOLATE_SHOP_MILESTONE -> "spending" HoppityEggType.CHOCOLATE_FACTORY_MILESTONE -> "reaching" else -> "" // Never happens } // List indexing is weird existingLore[replaceIndex - 1] = "§7Obtained by $operationFormat §6$displayAmount" existingLore[replaceIndex] = "§7all-time §6Chocolate." return existingLore } + NoNameShadowing:HotmData.kt$HotmData.Companion${ it.setCurrent(it.getTotal()) } + NoNameShadowing:LorenzVec.kt$LorenzVec.Companion$pitch + NoNameShadowing:LorenzVec.kt$LorenzVec.Companion$yaw + NoNameShadowing:Renderable.kt$Renderable.Companion.<no name provided>$posX + NoNameShadowing:Renderable.kt$Renderable.Companion.<no name provided>$posY + NoNameShadowing:Renderable.kt$Renderable.Companion.<no name provided>${ it.value?.contains(textInput.textBox, ignoreCase = true) ?: true } + NoNameShadowing:RenderableUtils.kt$RenderableUtils${ it != null } + NoNameShadowing:ReplaceRomanNumerals.kt$ReplaceRomanNumerals${ it.isValidRomanNumeral() && it.removeFormatting().romanToDecimal() != 2000 } + NoNameShadowing:RepoManager.kt$RepoManager${ unsuccessfulConstants.add(it) } + NoNameShadowing:RepoPatternManager.kt$RepoPatternManager${ it == '.' } + NoNameShadowing:Shimmy.kt$Shimmy.Companion$source + NoNameShadowing:SkyHanniBucketedItemTracker.kt$SkyHanniBucketedItemTracker${ ItemPriceSource.entries[it.ordinal] } + ReturnCount:AnitaMedalProfit.kt$AnitaMedalProfit$private fun readItem(slot: Int, item: ItemStack, table: MutableList<DisplayTableEntry>) + ReturnCount:BingoNextStepHelper.kt$BingoNextStepHelper$private fun readDescription(description: String): NextStep? + ReturnCount:BroodmotherFeatures.kt$BroodmotherFeatures$private fun onStageUpdate() + ReturnCount:ChatPeek.kt$ChatPeek$@JvmStatic fun peek(): Boolean + ReturnCount:ChestValue.kt$ChestValue$private fun isValidStorage(): Boolean + ReturnCount:CollectionTracker.kt$CollectionTracker$fun command(args: Array<String>) + ReturnCount:CompactBingoChat.kt$CompactBingoChat$private fun onSkyBlockLevelUp(message: String): Boolean + ReturnCount:CrimsonMinibossRespawnTimer.kt$CrimsonMinibossRespawnTimer$private fun updateArea() + ReturnCount:CropMoneyDisplay.kt$CropMoneyDisplay$private fun drawDisplay(): List<List<Any>> + ReturnCount:DamageIndicatorManager.kt$DamageIndicatorManager$private fun checkThorn(realHealth: Long, realMaxHealth: Long): String? + ReturnCount:DamageIndicatorManager.kt$DamageIndicatorManager$private fun getCustomHealth( entityData: EntityData, health: Long, entity: EntityLivingBase, maxHealth: Long, ): String? + ReturnCount:EnchantParser.kt$EnchantParser$private fun parseEnchants( loreList: MutableList<String>, enchants: Map<String, Int>, chatComponent: IChatComponent?, ) + ReturnCount:EstimatedItemValue.kt$EstimatedItemValue$private fun draw(stack: ItemStack): List<List<Any>> + ReturnCount:EstimatedItemValueCalculator.kt$EstimatedItemValueCalculator$private fun calculateStarPrice( internalName: NEUInternalName, inputStars: Int, ): Pair<EssenceItemUtils.EssenceUpgradePrice, Pair<Int, Int>>? + ReturnCount:FishingAPI.kt$FishingAPI$fun seaCreatureCount(entity: EntityArmorStand): Int + ReturnCount:GardenVisitorFeatures.kt$GardenVisitorFeatures$private fun showGui(): Boolean + ReturnCount:GraphEditor.kt$GraphEditor$private fun input() + ReturnCount:HideNotClickableItems.kt$HideNotClickableItems$private fun hideSalvage(chestName: String, stack: ItemStack): Boolean + ReturnCount:ItemDisplayOverlayFeatures.kt$ItemDisplayOverlayFeatures$private fun getStackTip(item: ItemStack): String? + ReturnCount:ItemNameResolver.kt$ItemNameResolver$internal fun getInternalNameOrNull(itemName: String): NEUInternalName? + ReturnCount:ItemPriceUtils.kt$ItemPriceUtils$fun NEUInternalName.getPriceOrNull( priceSource: ItemPriceSource = ItemPriceSource.BAZAAR_INSTANT_BUY, pastRecipes: List<PrimitiveRecipe> = emptyList(), ): Double? + ReturnCount:ItemUtils.kt$ItemUtils$private fun NEUInternalName.grabItemName(): String + ReturnCount:MinecraftConsoleFilter.kt$MinecraftConsoleFilter$override fun filter(event: LogEvent?): Filter.Result + ReturnCount:MiningEventTracker.kt$MiningEventTracker$private fun sendData(eventName: String, time: String?) + ReturnCount:MobDetection.kt$MobDetection$private fun entitySpawn(entity: EntityLivingBase, roughType: Mob.Type): Boolean + ReturnCount:MobFilter.kt$MobFilter$internal fun createSkyblockEntity(baseEntity: EntityLivingBase): MobResult + ReturnCount:MobFilter.kt$MobFilter$private fun armorStandOnlyMobs(baseEntity: EntityLivingBase, armorStand: EntityArmorStand): MobResult? + ReturnCount:MobFilter.kt$MobFilter$private fun exceptions(baseEntity: EntityLivingBase, nextEntity: EntityLivingBase?): MobResult? + ReturnCount:MobFinder.kt$MobFinder$private fun tryAddEntitySpider(entity: EntityLivingBase): EntityResult? + ReturnCount:MobFinder.kt$MobFinder$private fun tryAddRift(entity: EntityLivingBase): EntityResult? + ReturnCount:MultiFilter.kt$MultiFilter$fun matchResult(string: String): String? + ReturnCount:PacketTest.kt$PacketTest$private fun Packet<*>.print() + ReturnCount:PowderMiningChatFilter.kt$PowderMiningChatFilter$@Suppress("CyclomaticComplexMethod") fun block(message: String): String? + ReturnCount:PurseAPI.kt$PurseAPI$private fun getCause(diff: Double): PurseChangeCause + ReturnCount:QuestLoader.kt$QuestLoader$private fun addQuest(name: String, state: QuestState, needAmount: Int): Quest + ReturnCount:ShowFishingItemName.kt$ShowFishingItemName$fun inCorrectArea(): Boolean + ReturnCount:SkillAPI.kt$SkillAPI$fun onCommand(it: Array<String>) + ReturnCount:SkyHanniConfigSearchResetCommand.kt$SkyHanniConfigSearchResetCommand$private suspend fun setCommand(args: Array<String>): String + SpreadOperator:ItemUtils.kt$ItemUtils$(tag, displayName, *lore.toTypedArray()) + SpreadOperator:LimboPlaytime.kt$LimboPlaytime$( itemID.getItemStack().item, ITEM_NAME, *createItemLore() ) + SpreadOperator:Text.kt$Text$(*component.toTypedArray(), separator = separator) + TooManyFunctions:CollectionUtils.kt$CollectionUtils + TooManyFunctions:DailyQuestHelper.kt$DailyQuestHelper + TooManyFunctions:EstimatedItemValueCalculator.kt$EstimatedItemValueCalculator + TooManyFunctions:GuiRenderUtils.kt$GuiRenderUtils + TooManyFunctions:HypixelCommands.kt$HypixelCommands + TooManyFunctions:InventoryUtils.kt$InventoryUtils + TooManyFunctions:LocationUtils.kt$LocationUtils + TooManyFunctions:LorenzUtils.kt$LorenzUtils + TooManyFunctions:LorenzVec.kt$LorenzVec + TooManyFunctions:MobFinder.kt$MobFinder + TooManyFunctions:NumberUtil.kt$NumberUtil + TooManyFunctions:RegexUtils.kt$RegexUtils + TooManyFunctions:RenderUtils.kt$RenderUtils + TooManyFunctions:Renderable.kt$Renderable$Companion + TooManyFunctions:ScoreboardElements.kt$at.hannibal2.skyhanni.features.gui.customscoreboard.ScoreboardElements.kt + TooManyFunctions:ScoreboardEvent.kt$at.hannibal2.skyhanni.features.gui.customscoreboard.ScoreboardEvent.kt + TooManyFunctions:SkyBlockItemModifierUtils.kt$SkyBlockItemModifierUtils + TooManyFunctions:StringUtils.kt$StringUtils + UnsafeCallOnNullableType:BasketWaypoints.kt$BasketWaypoints$Basket.entries.minByOrNull { it.waypoint.distanceSqToPlayer() }!! + UnsafeCallOnNullableType:BasketWaypoints.kt$BasketWaypoints$notFoundBaskets.minByOrNull { it.waypoint.distanceSqToPlayer() }!! + UnsafeCallOnNullableType:BucketedItemTrackerData.kt$BucketedItemTrackerData$it.value[internalName]?.hidden!! + UnsafeCallOnNullableType:ChocolateFactoryDataLoader.kt$ChocolateFactoryDataLoader$upgradeCost!! + UnsafeCallOnNullableType:CollectionUtils.kt$CollectionUtils$this.merge(key, number, Double::plus)!! + UnsafeCallOnNullableType:CollectionUtils.kt$CollectionUtils$this.merge(key, number, Float::plus)!! + UnsafeCallOnNullableType:CollectionUtils.kt$CollectionUtils$this.merge(key, number, Int::plus)!! + UnsafeCallOnNullableType:CollectionUtils.kt$CollectionUtils$this.merge(key, number, Long::plus)!! + UnsafeCallOnNullableType:CombatUtils.kt$CombatUtils$a!! + UnsafeCallOnNullableType:CompactBestiaryChatMessage.kt$CompactBestiaryChatMessage$it.groups[1]!! + UnsafeCallOnNullableType:ConfigManager.kt$ConfigManager$file!! + UnsafeCallOnNullableType:CorpseTracker.kt$CorpseTracker$applicableKeys.first().key!! + UnsafeCallOnNullableType:CropMoneyDisplay.kt$CropMoneyDisplay$cropNames[internalName]!! + UnsafeCallOnNullableType:DailyMiniBossHelper.kt$DailyMiniBossHelper$getByDisplayName(name)!! + UnsafeCallOnNullableType:DamageIndicatorManager.kt$DamageIndicatorManager$data.deathLocation!! + UnsafeCallOnNullableType:DefaultConfigFeatures.kt$DefaultConfigFeatures$resetSuggestionState[cat]!! + UnsafeCallOnNullableType:DefaultConfigOptionGui.kt$DefaultConfigOptionGui$resetSuggestionState[cat]!! + UnsafeCallOnNullableType:DicerRngDropTracker.kt$DicerRngDropTracker$event.toolItem!! + UnsafeCallOnNullableType:DiscordStatus.kt$ownerRegex.find(colorlessLine)!! + UnsafeCallOnNullableType:DungeonAPI.kt$DungeonAPI$dungeonFloor!! + UnsafeCallOnNullableType:EasterEggWaypoints.kt$EasterEggWaypoints$EasterEgg.entries.minByOrNull { it.waypoint.distanceSqToPlayer() }!! + UnsafeCallOnNullableType:EasterEggWaypoints.kt$EasterEggWaypoints$notFoundEggs.minByOrNull { it.waypoint.distanceSqToPlayer() }!! + UnsafeCallOnNullableType:EntityMovementData.kt$EntityMovementData$entityLocation[entity]!! + UnsafeCallOnNullableType:EntityOutlineRenderer.kt$EntityOutlineRenderer$entityRenderCache.noXrayCache!! + UnsafeCallOnNullableType:EntityOutlineRenderer.kt$EntityOutlineRenderer$entityRenderCache.xrayCache!! + UnsafeCallOnNullableType:EntityOutlineRenderer.kt$EntityOutlineRenderer$frameToCopy!! + UnsafeCallOnNullableType:EntityOutlineRenderer.kt$EntityOutlineRenderer$frameToPaste!! + UnsafeCallOnNullableType:EntityOutlineRenderer.kt$EntityOutlineRenderer$isAntialiasing!! + UnsafeCallOnNullableType:EntityOutlineRenderer.kt$EntityOutlineRenderer$isFastRender!! + UnsafeCallOnNullableType:EntityOutlineRenderer.kt$EntityOutlineRenderer$isShaders!! + UnsafeCallOnNullableType:FFGuideGUI.kt$FFGuideGUI$currentCrop!! + UnsafeCallOnNullableType:FarmingContestAPI.kt$FarmingContestAPI$contestCrop!! + UnsafeCallOnNullableType:FarmingContestAPI.kt$FarmingContestAPI$contests[bracket]!! + UnsafeCallOnNullableType:FarmingContestAPI.kt$FarmingContestAPI$currentCrop!! + UnsafeCallOnNullableType:FarmingWeightDisplay.kt$FarmingWeightDisplay$weightPerCrop[CropType.CACTUS]!! + UnsafeCallOnNullableType:FarmingWeightDisplay.kt$FarmingWeightDisplay$weightPerCrop[CropType.SUGAR_CANE]!! + UnsafeCallOnNullableType:FeatureToggleProcessor.kt$FeatureToggleProcessor$latestCategory!! + UnsafeCallOnNullableType:FeatureTogglesByDefaultAdapter.kt$FeatureTogglesByDefaultAdapter$gson!! + UnsafeCallOnNullableType:FortuneUpgrades.kt$FortuneUpgrades$nextTalisman.upgradeCost?.first!! + UnsafeCallOnNullableType:GardenComposterUpgradesData.kt$GardenComposterUpgradesData$ComposterUpgrade.getByName(name)!! + UnsafeCallOnNullableType:GardenCropMilestoneDisplay.kt$GardenCropMilestoneDisplay$cultivatingData[crop]!! + UnsafeCallOnNullableType:GardenCropMilestonesCommunityFix.kt$GardenCropMilestonesCommunityFix$map[crop]!! + UnsafeCallOnNullableType:GardenPlotIcon.kt$GardenPlotIcon$originalStack[index]!! + UnsafeCallOnNullableType:GhostCounter.kt$GhostCounter$storage?.totalMF!! + UnsafeCallOnNullableType:Graph.kt$Graph.Companion$position!! + UnsafeCallOnNullableType:Graph.kt$distances.distances[end]!! + UnsafeCallOnNullableType:GriffinBurrowHelper.kt$GriffinBurrowHelper$particleBurrows[targetLocation]!! + UnsafeCallOnNullableType:HoppityCallWarning.kt$HoppityCallWarning$acceptUUID!! + UnsafeCallOnNullableType:IslandGraphs.kt$IslandGraphs$currentTarget!! + UnsafeCallOnNullableType:ItemBlink.kt$ItemBlink$offsets[item]!! + UnsafeCallOnNullableType:ItemPickupLog.kt$ItemPickupLog$listToCheckAgainst[key]!! + UnsafeCallOnNullableType:ItemPickupLog.kt$ItemPickupLog$listToCheckAgainst[key]?.second!! + UnsafeCallOnNullableType:ItemStackTypeAdapterFactory.kt$ItemStackTypeAdapterFactory$gson!! + UnsafeCallOnNullableType:ItemUtils.kt$ItemUtils$itemAmountCache[input]!! + UnsafeCallOnNullableType:JacobContestTimeNeeded.kt$JacobContestTimeNeeded$map[crop]!! + UnsafeCallOnNullableType:KSerializable.kt$KotlinTypeAdapterFactory$kotlinClass.memberProperties.find { it.name == param.name }!! + UnsafeCallOnNullableType:KSerializable.kt$KotlinTypeAdapterFactory$param.name!! + UnsafeCallOnNullableType:LorenzEvent.kt$LorenzEvent$this::class.simpleName!! + UnsafeCallOnNullableType:MinionFeatures.kt$MinionFeatures$newMinion!! + UnsafeCallOnNullableType:MobFinder.kt$MobFinder$floor6GiantsSeparateDelay[uuid]!! + UnsafeCallOnNullableType:NavigationHelper.kt$NavigationHelper$distances[node]!! + UnsafeCallOnNullableType:NumberUtil.kt$NumberUtil$romanSymbols[this]!! + UnsafeCallOnNullableType:PositionList.kt$PositionList$configLink!! + UnsafeCallOnNullableType:ReminderManager.kt$ReminderManager$storage[args.drop(1).first()]!! + UnsafeCallOnNullableType:ReminderManager.kt$ReminderManager$storage[args.first()]!! + UnsafeCallOnNullableType:RenderEntityOutlineEvent.kt$RenderEntityOutlineEvent$entitiesToChooseFrom!! + UnsafeCallOnNullableType:RenderEntityOutlineEvent.kt$RenderEntityOutlineEvent$entitiesToOutline!! + UnsafeCallOnNullableType:RenderGlobalHook.kt$RenderGlobalHook$camera!! + UnsafeCallOnNullableType:RenderLivingEntityHelper.kt$RenderLivingEntityHelper$entityColorCondition[entity]!! + UnsafeCallOnNullableType:RenderLivingEntityHelper.kt$RenderLivingEntityHelper$entityColorMap[entity]!! + UnsafeCallOnNullableType:RenderUtils.kt$RenderUtils$it.name!! + UnsafeCallOnNullableType:RepoManager.kt$RepoManager$latestRepoCommit!! + UnsafeCallOnNullableType:RepoUtils.kt$RepoUtils$file!! + UnsafeCallOnNullableType:SackAPI.kt$SackAPI$match.groups[1]!! + UnsafeCallOnNullableType:SackAPI.kt$SackAPI$match.groups[2]!! + UnsafeCallOnNullableType:SackAPI.kt$SackAPI$match.groups[3]!! + UnsafeCallOnNullableType:SackAPI.kt$SackAPI$oldData!! + UnsafeCallOnNullableType:SkyHanniBucketedItemTracker.kt$SkyHanniBucketedItemTracker$it.get(DisplayMode.SESSION).getItemsProp()[internalName]!! + UnsafeCallOnNullableType:SkyHanniBucketedItemTracker.kt$SkyHanniBucketedItemTracker$it.get(DisplayMode.TOTAL).getItemsProp()[internalName]!! + UnsafeCallOnNullableType:SkyHanniMod.kt$SkyHanniMod.Companion$Loader.instance().indexedModList[MODID]!! + UnsafeCallOnNullableType:SoopyGuessBurrow.kt$SoopyGuessBurrow$distance!! + UnsafeCallOnNullableType:SoopyGuessBurrow.kt$SoopyGuessBurrow$distance2!! + UnsafeCallOnNullableType:SoopyGuessBurrow.kt$SoopyGuessBurrow$firstParticlePoint?.distance(pos)!! + UnsafeCallOnNullableType:SoopyGuessBurrow.kt$SoopyGuessBurrow$lastParticlePoint2!! + UnsafeCallOnNullableType:SoopyGuessBurrow.kt$SoopyGuessBurrow$lastParticlePoint2?.distance(particlePoint!!)!! + UnsafeCallOnNullableType:SoopyGuessBurrow.kt$SoopyGuessBurrow$particlePoint!! + UnsafeCallOnNullableType:SoopyGuessBurrow.kt$SoopyGuessBurrow$particlePoint?.subtract(lastParticlePoint2!!)!! + UnsafeCallOnNullableType:SummoningSoulsName.kt$SummoningSoulsName$mobsName.getOrNull(nearestMob)!! + UnsafeCallOnNullableType:SuperpairsClicksAlert.kt$SuperpairsClicksAlert$match.groups[1]!! + UnsafeCallOnNullableType:TiaRelayWaypoints.kt$TiaRelayWaypoints$waypointName!! + UnsafeCallOnNullableType:Translator.kt$Translator$messageContentRegex.find(message)!! + UnsafeCallOnNullableType:TrevorFeatures.kt$TrevorFeatures$TrevorSolver.currentMob!! + UnsafeCallOnNullableType:TrevorSolver.kt$TrevorSolver$currentMob!! + UnsafeCallOnNullableType:TunnelsMaps.kt$TunnelsMaps$campfire.name!! + UnsafeCallOnNullableType:UpdateManager.kt$UpdateManager$potentialUpdate!! + UnsafeCallOnNullableType:VisitorRewardWarning.kt$VisitorRewardWarning$visitor.totalPrice!! + UnsafeCallOnNullableType:VisitorRewardWarning.kt$VisitorRewardWarning$visitor.totalReward!! + UnusedParameter:SkyHanniDebugsAndTests.kt$SkyHanniDebugsAndTests$args: Array<String> + UseIsNullOrEmpty:ItemUtils.kt$ItemUtils$name == null || name.isEmpty() + UseOrEmpty:SkyHanniDebugsAndTests.kt$SkyHanniDebugsAndTests$event.originalOre?.let { "$it " } ?: "" + VarCouldBeVal:AdvancedPlayerList.kt$AdvancedPlayerList$private var randomOrderCache = TimeLimitedCache<String, Int>(20.minutes) + VarCouldBeVal:BestiaryData.kt$BestiaryData$private var indexes = listOf( 10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43 ) + VarCouldBeVal:BucketedItemTrackerData.kt$BucketedItemTrackerData$@Expose private var bucketedItems: MutableMap<E, MutableMap<NEUInternalName, TrackedItem>> = HashMap() + VarCouldBeVal:CarnivalZombieShootout.kt$CarnivalZombieShootout$private var lastUpdate = Updates(SimpleTimeMark.farPast(), SimpleTimeMark.farPast()) + VarCouldBeVal:ChocolateFactoryStrayTracker.kt$ChocolateFactoryStrayTracker$private var claimedStraysSlots = mutableListOf<Int>() + VarCouldBeVal:CompactBestiaryChatMessage.kt$CompactBestiaryChatMessage$private var bestiaryDescription = mutableListOf<String>() + VarCouldBeVal:CraftRoomHolographicMob.kt$CraftRoomHolographicMob$private var entityToHolographicEntity = mapOf( EntityZombie::class.java to HolographicEntities.zombie, EntitySlime::class.java to HolographicEntities.slime, EntityCaveSpider::class.java to HolographicEntities.caveSpider, ) + VarCouldBeVal:CustomWardrobe.kt$CustomWardrobe$private var guiName = "Custom Wardrobe" + VarCouldBeVal:Enchant.kt$Enchant$@Expose private var goodLevel = 0 + VarCouldBeVal:Enchant.kt$Enchant$@Expose private var maxLevel = 0 + VarCouldBeVal:Enchant.kt$Enchant.Stacking$@Expose private var nbtNum: String? = null + VarCouldBeVal:Enchant.kt$Enchant.Stacking$@Expose private var stackLevel: TreeSet<Int>? = null + VarCouldBeVal:ErrorManager.kt$ErrorManager$private var cache = TimeLimitedSet<Pair<String, Int>>(10.minutes) + VarCouldBeVal:EstimatedItemValueCalculator.kt$EstimatedItemValueCalculator$var comboPrice = combo.asInternalName().getPriceOrNull() + VarCouldBeVal:EstimatedItemValueCalculator.kt$EstimatedItemValueCalculator$var internalName = removeKuudraArmorPrefix(stack.getInternalName().asString().removePrefix("VANQUISHED_")) + VarCouldBeVal:EstimatedItemValueCalculator.kt$EstimatedItemValueCalculator$var nameColor = if (!useless) "§9" else "§7" + VarCouldBeVal:ExperimentsProfitTracker.kt$ExperimentsProfitTracker$private var currentBottlesInInventory = mutableMapOf<NEUInternalName, Int>() + VarCouldBeVal:ExperimentsProfitTracker.kt$ExperimentsProfitTracker$private var lastBottlesInInventory = mutableMapOf<NEUInternalName, Int>() + VarCouldBeVal:FarmingWeightDisplay.kt$FarmingWeightDisplay$private var nextPlayers = mutableListOf<UpcomingLeaderboardPlayer>() + VarCouldBeVal:FlareDisplay.kt$FlareDisplay$private var flares = mutableListOf<Flare>() + VarCouldBeVal:FontRendererHook.kt$FontRendererHook$private var CHROMA_COLOR: Int = -0x1 + VarCouldBeVal:FontRendererHook.kt$FontRendererHook$private var CHROMA_COLOR_SHADOW: Int = -0xAAAAAB + VarCouldBeVal:GardenPlotIcon.kt$GardenPlotIcon$private var cachedStack = mutableMapOf<Int, ItemStack>() + VarCouldBeVal:GardenPlotIcon.kt$GardenPlotIcon$private var originalStack = mutableMapOf<Int, ItemStack>() + VarCouldBeVal:GardenPlotMenuHighlighting.kt$GardenPlotMenuHighlighting$private var highlightedPlots = mutableMapOf<GardenPlotAPI.Plot, PlotStatusType>() + VarCouldBeVal:GardenVisitorColorNames.kt$GardenVisitorColorNames$private var visitorColors = mutableMapOf<String, String>() // name -> color code + VarCouldBeVal:GhostData.kt$GhostData$private var session = mutableMapOf( Option.KILLS to 0.0, Option.SORROWCOUNT to 0.0, Option.VOLTACOUNT to 0.0, Option.PLASMACOUNT to 0.0, Option.GHOSTLYBOOTS to 0.0, Option.BAGOFCASH to 0.0, Option.TOTALDROPS to 0.0, Option.SCAVENGERCOINS to 0.0, Option.MAXKILLCOMBO to 0.0, Option.SKILLXPGAINED to 0.0 ) + VarCouldBeVal:GraphEditor.kt$GraphEditor$var vector = LocationUtils.calculatePlayerFacingDirection() + VarCouldBeVal:HoppityCollectionStats.kt$HoppityCollectionStats$private var highlightMap = mutableMapOf<String, LorenzColor>() + VarCouldBeVal:HoppityEggLocations.kt$HoppityEggLocations$// TODO add gui/command to show total data/missing islands private var collectedEggStorage: MutableMap<IslandType, MutableSet<LorenzVec>> get() = ChocolateFactoryAPI.profileStorage?.collectedEggLocations ?: mutableMapOf() set(value) { ChocolateFactoryAPI.profileStorage?.collectedEggLocations = value } + VarCouldBeVal:HoppityNpc.kt$HoppityNpc$private var slotsToHighlight = mutableSetOf<Int>() + VarCouldBeVal:IslandAreas.kt$IslandAreas$var suffix = "" + VarCouldBeVal:ItemPickupLog.kt$ItemPickupLog$private var itemList = mutableMapOf<Int, Pair<ItemStack, Int>>() + VarCouldBeVal:ItemPickupLog.kt$ItemPickupLog$private var itemsAddedToInventory = mutableMapOf<Int, PickupEntry>() + VarCouldBeVal:ItemPickupLog.kt$ItemPickupLog$private var itemsRemovedFromInventory = mutableMapOf<Int, PickupEntry>() + VarCouldBeVal:LocationUtils.kt$LocationUtils$var yaw = LocationUtils.calculatePlayerYaw() + 180 + VarCouldBeVal:LorenzLogger.kt$LorenzLogger.Companion$private var LOG_DIRECTORY = File("config/skyhanni/logs") + VarCouldBeVal:MobDetection.kt$MobDetection$private var shouldClear: AtomicBoolean = AtomicBoolean(false) + VarCouldBeVal:MobFinder.kt$MobFinder$private var floor2summonsDiedOnce = mutableListOf<EntityOtherPlayerMP>() + VarCouldBeVal:MobFinder.kt$MobFinder$private var floor6GiantsSeparateDelay = mutableMapOf<UUID, Pair<Long, BossType>>() + VarCouldBeVal:MobFinder.kt$MobFinder$private var guardians = mutableListOf<EntityGuardian>() + VarCouldBeVal:NeuReforgeJson.kt$NeuReforgeJson$private lateinit var itemTypeField: Pair<String, List<NEUInternalName>> + VarCouldBeVal:PowerStoneGuideFeatures.kt$PowerStoneGuideFeatures$private var missing = mutableMapOf<Int, NEUInternalName>() + VarCouldBeVal:PunchcardHighlight.kt$PunchcardHighlight$private var playerQueue = mutableListOf<String>() + VarCouldBeVal:QuiverWarning.kt$QuiverWarning$private var arrowsInInstance = mutableSetOf<ArrowType>() + VarCouldBeVal:ReforgeHelper.kt$ReforgeHelper$private var waitForChat = AtomicBoolean(false) + VarCouldBeVal:ReminderManager.kt$ReminderManager$private var listPage = 1 + VarCouldBeVal:RepoPatternGui.kt$RepoPatternGui$private var searchCache = ObservableList(mutableListOf<RepoPatternInfo>()) + VarCouldBeVal:RepoPatternManager.kt$RepoPatternManager$/** * Map containing all keys and their repo patterns. Used for filling in new regexes after an update, and for * checking duplicate registrations. */ private var usedKeys: NavigableMap<String, CommonPatternInfo<*, *>> = TreeMap() + VarCouldBeVal:RepoPatternManager.kt$RepoPatternManager$/** * Map containing the exclusive owner of a regex key */ private var exclusivity: MutableMap<String, RepoPatternKeyOwner> = mutableMapOf() + VarCouldBeVal:SeaCreatureFeatures.kt$SeaCreatureFeatures$private var entityIds = TimeLimitedSet<Int>(6.minutes) + VarCouldBeVal:SeaCreatureFeatures.kt$SeaCreatureFeatures$private var rareSeaCreatures = TimeLimitedSet<Mob>(6.minutes) + VarCouldBeVal:ShowFishingItemName.kt$ShowFishingItemName$private var itemsOnGround = TimeLimitedCache<EntityItem, String>(750.milliseconds) + VarCouldBeVal:SkyblockGuideHighlightFeature.kt$SkyblockGuideHighlightFeature.Companion$private var missing = mutableSetOf<Int>() + VarCouldBeVal:SlayerItemsOnGround.kt$SlayerItemsOnGround$private var itemsOnGround = TimeLimitedCache<EntityItem, String>(2.seconds) + VarCouldBeVal:SoopyGuessBurrow.kt$SoopyGuessBurrow$private var dingSlope = mutableListOf<Float>() + VarCouldBeVal:SoopyGuessBurrow.kt$SoopyGuessBurrow$private var locations = mutableListOf<LorenzVec>() + VarCouldBeVal:TiaRelayHelper.kt$TiaRelayHelper$private var resultDisplay = mutableMapOf<Int, Int>() + VarCouldBeVal:TiaRelayHelper.kt$TiaRelayHelper$private var sounds = mutableMapOf<Int, Sound>() + VarCouldBeVal:TrevorFeatures.kt$TrevorFeatures$private var backupTrapperID: Int = 17 + VarCouldBeVal:TrevorFeatures.kt$TrevorFeatures$private var trapperID: Int = 56 + VarCouldBeVal:TrophyFishDisplay.kt$TrophyFishDisplay$private var recentlyDroppedTrophies = TimeLimitedCache<NEUInternalName, TrophyRarity>(5.seconds) + VarCouldBeVal:UserLuckBreakdown.kt$UserLuckBreakdown$private var fillerID = "STAINED_GLASS_PANE".asInternalName() + VarCouldBeVal:UserLuckBreakdown.kt$UserLuckBreakdown$private var limboID = "ENDER_PEARL".asInternalName() + VarCouldBeVal:UserLuckBreakdown.kt$UserLuckBreakdown$private var skillOverflowLuck = mutableMapOf<SkillType, Int>() + VarCouldBeVal:UserLuckBreakdown.kt$UserLuckBreakdown$private var skillsID = "DIAMOND_SWORD".asInternalName() + + diff --git a/detekt/build.gradle.kts b/detekt/build.gradle.kts new file mode 100644 index 000000000000..ce0de9ace8ce --- /dev/null +++ b/detekt/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + kotlin("jvm") + id("com.google.devtools.ksp") +} + +dependencies { + implementation("io.gitlab.arturbosch.detekt:detekt-api:1.23.7") + ksp(libs.autoservice.ksp) + implementation(libs.autoservice.annotations) + implementation("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.7") + testImplementation("io.kotest:kotest-assertions-core:5.9.1") + testImplementation("io.gitlab.arturbosch.detekt:detekt-test:1.23.7") +} diff --git a/detekt/detekt.yml b/detekt/detekt.yml new file mode 100644 index 000000000000..014ca8c5a309 --- /dev/null +++ b/detekt/detekt.yml @@ -0,0 +1,134 @@ + +config: + validation: true + +GrammarRules: + active: true + AvoidBritishSpelling: # custom rule to prefer american spellings over british ones + active: true + +FormattingRules: + active: true + CustomCommentSpacing: + active: true + +ImportRules: + active: true + CustomImportOrdering: + active: true + + +style: + MagicNumber: # I, Linnea Gräf, of sound mind and body, disagree with disabling this rule + active: false + UnusedParameter: + active: true + ignoreAnnotated: + - 'SubscribeEvent' + - 'HandleEvent' + - 'Mod.EventHandler' + ReturnCount: + active: true + max: 5 + excludeGuardClauses: true + ignoreAnnotated: + - 'SubscribeEvent' + - 'HandleEvent' + - 'Mod.EventHandler' + MaxLineLength: + active: true + maxLineLength: 140 + excludeCommentStatements: true + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 3 + UnnecessaryAbstractClass: # gets horrendously messed up with Event classes + active: false + UnusedPrivateMember: # gets tripped up by API methods + active: false + UnusedPrivateProperty: # loops that don't use their iterator + active: true + allowedNames: "^(unused|_)$" + UseCheckOrError: + active: false + ForbiddenComment: # every TODO gets flagged + active: false + DestructuringDeclarationWithTooManyEntries: # too aggressive + active: true + maxDestructuringEntries: 5 + +formatting: + MaximumLineLength: # ktlint - handled by detekt + active: false + MultiLineIfElse: + active: false + ArgumentListWrapping: # ktlint - way too aggressive + active: false + NoBlankLineBeforeRbrace: # pedantic + active: false + NoConsecutiveBlankLines: # pedantic + active: false + NoEmptyFirstLineInMethodBlock: # pedantic + active: false + ParameterListWrapping: # pedantic, can be useful in compact code + active: false + CommentSpacing: # handled by custom rule + active: false + SpacingBetweenDeclarationsWithAnnotations: # nah + active: false + SpacingBetweenDeclarationsWithComments: # also nah + active: false + ImportOrdering: # handled by custom rule + active: false + +complexity: + CyclomaticComplexMethod: # default threshold of 15, caught almost every complex method + active: true + threshold: 25 + ignoreAnnotated: + - 'SubscribeEvent' + - 'HandleEvent' + - 'Mod.EventHandler' + LongParameterList: # too aggressive, classes can need a lot of params + active: false + NestedBlockDepth: # too aggressive + active: false + TooManyFunctions: # ktlint - also way too aggressive by default (11 on all file types) + active: true + thresholdInFiles: 15 + thresholdInClasses: 20 + thresholdInInterfaces: 20 + thresholdInObjects: 20 + thresholdInEnums: 11 + ignoreAnnotated: + - 'SkyHanniModule' + ComplexCondition: # aggressive by default, at a complexity of 4 + active: true + threshold: 6 + LongMethod: # default max length of 60, caught way too much + active: true + threshold: 100 + ignoreAnnotated: + - 'SubscribeEvent' + - 'HandleEvent' + - 'Mod.EventHandler' + +exceptions: + SwallowedException: # there are valid reasons to do this + active: false + ThrowingExceptionsWithoutMessageOrCause: # again, valid reasons + active: false + TooGenericExceptionCaught: # sometimes you just need to catch Exception + active: false + TooGenericExceptionThrown: # we don't have our own custom exceptions + active: false + +naming: + ConstructorParameterNaming: # pedantic + active: false + +potential-bugs: + DoubleMutabilityForCollection: # went crazy about all the mutable collections + active: false + HasPlatformType: # false positives on config get() methods + active: false diff --git a/detekt/src/main/kotlin/PreprocessingPatterns.kt b/detekt/src/main/kotlin/PreprocessingPatterns.kt new file mode 100644 index 000000000000..695f088cfe22 --- /dev/null +++ b/detekt/src/main/kotlin/PreprocessingPatterns.kt @@ -0,0 +1,19 @@ +package at.hannibal2.skyhanni.detektrules + +enum class PreprocessingPattern(val text: String) { + IF("#if"), + ELSE("#else"), + ELSEIF("#elseif"), + ENDIF("#endif"), + DOLLAR_DOLLAR("$$"), + ; + + val asComment: String + get() = "//$text" + + companion object { + fun String.containsPreprocessingPattern(): Boolean { + return entries.any { this.contains(it.text) } + } + } +} diff --git a/detekt/src/main/kotlin/formatting/CustomCommentSpacing.kt b/detekt/src/main/kotlin/formatting/CustomCommentSpacing.kt new file mode 100644 index 000000000000..db9f2f8a75d4 --- /dev/null +++ b/detekt/src/main/kotlin/formatting/CustomCommentSpacing.kt @@ -0,0 +1,43 @@ +package at.hannibal2.skyhanni.detektrules.formatting + +import at.hannibal2.skyhanni.detektrules.PreprocessingPattern.Companion.containsPreprocessingPattern +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import org.jetbrains.kotlin.com.intellij.psi.PsiComment + +class CustomCommentSpacing(config: Config) : Rule(config) { + override val issue = Issue( + "CustomCommentSpacing", + Severity.Style, + "Enforces custom spacing rules for comments.", + Debt.FIVE_MINS + ) + + + override fun visitComment(comment: PsiComment) { + if (comment.text.containsPreprocessingPattern()) return + + /** + * REGEX-TEST: // Test comment + * REGEX-TEST: /* Test comment */ + */ + val commentRegex = Regex("""^(?:\/{2}|\/\*)(?:\s.*|$)""", RegexOption.DOT_MATCHES_ALL) + if (!commentRegex.matches(comment.text)) { + report( + CodeSmell( + issue, + Entity.from(comment), + "Expected space after opening comment." + ) + ) + } + + // Fallback to super (ostensibly a no-check) + super.visitComment(comment) + } +} diff --git a/detekt/src/main/kotlin/formatting/FormattingRuleSetProvider.kt b/detekt/src/main/kotlin/formatting/FormattingRuleSetProvider.kt new file mode 100644 index 000000000000..a0a969bcf503 --- /dev/null +++ b/detekt/src/main/kotlin/formatting/FormattingRuleSetProvider.kt @@ -0,0 +1,17 @@ +package at.hannibal2.skyhanni.detektrules.formatting + +import com.google.auto.service.AutoService +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider + +@AutoService(RuleSetProvider::class) +class FormattingRuleSetProvider : RuleSetProvider { + override val ruleSetId: String = "FormattingRules" + + override fun instance(config: Config): RuleSet { + return RuleSet(ruleSetId, listOf( + CustomCommentSpacing(config) + )) + } +} diff --git a/detekt/src/main/kotlin/grammar/AvoidBritishSpelling.kt b/detekt/src/main/kotlin/grammar/AvoidBritishSpelling.kt new file mode 100644 index 000000000000..6a4ea3d61eb4 --- /dev/null +++ b/detekt/src/main/kotlin/grammar/AvoidBritishSpelling.kt @@ -0,0 +1,46 @@ +package at.hannibal2.skyhanni.detektrules.grammar + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import org.jetbrains.kotlin.psi.KtStringTemplateExpression + +/** + * This rule reports all usages of the british spelling over the american spelling in the codebase, + * this will ignore any type annotations, i.e., `@ConfigEditorColour` will not be reported. + */ +class AvoidBritishSpelling(config: Config) : Rule(config) { + override val issue = Issue( + "AvoidBritishSpelling", + Severity.Style, + "Avoid using the word british spelling over american spelling.", + Debt.FIVE_MINS, + ) + + private val scannedWords = mapOf( + "colour" to "color", + "armour" to "armor", + ) + + override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) { + val text = + expression.text // Be aware .getText() returns the entire span of this template, including variable names contained within. This should be rare enough of a problem for us to not care about it. + + for (word in scannedWords) { + if (text.contains(word.key, ignoreCase = true)) { + report( + CodeSmell( + issue, + Entity.from(expression), + "Avoid using the word '${word.key}' in code, '${word.value}' is preferred instead.", + ), + ) + } + } + super.visitStringTemplateExpression(expression) + } +} diff --git a/detekt/src/main/kotlin/grammar/GrammarRuleSetProvider.kt b/detekt/src/main/kotlin/grammar/GrammarRuleSetProvider.kt new file mode 100644 index 000000000000..8001fe42b5b2 --- /dev/null +++ b/detekt/src/main/kotlin/grammar/GrammarRuleSetProvider.kt @@ -0,0 +1,20 @@ +package at.hannibal2.skyhanni.detektrules.grammar + +import com.google.auto.service.AutoService +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider + +@AutoService(RuleSetProvider::class) +class GrammarRuleSetProvider : RuleSetProvider { + override val ruleSetId: String = "GrammarRules" + + override fun instance(config: Config): RuleSet { + return RuleSet( + ruleSetId, + listOf( + AvoidBritishSpelling(config), + ), + ) + } +} diff --git a/detekt/src/main/kotlin/imports/CustomImportOrdering.kt b/detekt/src/main/kotlin/imports/CustomImportOrdering.kt new file mode 100644 index 000000000000..9e0302153d0a --- /dev/null +++ b/detekt/src/main/kotlin/imports/CustomImportOrdering.kt @@ -0,0 +1,114 @@ +package at.hannibal2.skyhanni.detektrules.imports + +import at.hannibal2.skyhanni.detektrules.PreprocessingPattern +import at.hannibal2.skyhanni.detektrules.PreprocessingPattern.Companion.containsPreprocessingPattern +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.KtImportList + +class CustomImportOrdering(config: Config) : Rule(config) { + override val issue = Issue( + "CustomImportOrdering", + Severity.Style, + "Enforces correct import ordering, taking into account preprocessed imports.", + Debt.FIVE_MINS, + ) + + companion object { + private val importOrder = ImportSorter() + + private val packageImportOrdering = listOf("java.", "javax.", "kotlin.") + + private class ImportSorter : Comparator { + override fun compare( + import1: KtImportDirective, + import2: KtImportDirective, + ): Int { + val importPath1 = import1.importPath!!.pathStr + val importPath2 = import2.importPath!!.pathStr + + val isTypeAlias1 = import1.aliasName != null + val isTypeAlias2 = import2.aliasName != null + + val index1 = packageImportOrdering.indexOfFirst { importPath1.startsWith(it) } + val index2 = packageImportOrdering.indexOfFirst { importPath2.startsWith(it) } + + return when { + isTypeAlias1 && isTypeAlias2 -> importPath1.compareTo(importPath2) + isTypeAlias1 && !isTypeAlias2 -> 1 + !isTypeAlias1 && isTypeAlias2 -> -1 + index1 == -1 && index2 == -1 -> importPath1.compareTo(importPath2) + index1 == -1 -> -1 + index2 == -1 -> 1 + else -> index1.compareTo(index2) + } + } + } + } + + private fun isImportsCorrectlyOrdered(imports: List, rawText: List): Boolean { + if (rawText.any { it.isBlank() }) { + return false + } + + var inPreprocess = false + val linesToIgnore = mutableListOf() + + for (line in rawText) { + if (line.contains(PreprocessingPattern.IF.asComment)) { + inPreprocess = true + continue + } + if (line.contains(PreprocessingPattern.ENDIF.asComment)) { + inPreprocess = false + continue + } + if (line.contains(PreprocessingPattern.DOLLAR_DOLLAR.asComment)) { + continue + } + if (inPreprocess) { + linesToIgnore.add(line) + } + } + + val originalImports = rawText.filter { !it.containsPreprocessingPattern() && !linesToIgnore.contains(it) } + val formattedOriginal = originalImports.joinToString("\n") { it } + + val expectedImports = imports.sortedWith(importOrder).map { "import ${it.importPath}" } + val formattedExpected = expectedImports.filter { !linesToIgnore.contains(it) }.joinToString("\n") + + return formattedOriginal == formattedExpected + } + + override fun visitImportList(importList: KtImportList) { + + val testEntity = Entity.from(importList) + + val rawText = importList.text.trim() + if (rawText.isBlank()) { + return + } + + val importsCorrect = isImportsCorrectlyOrdered(importList.imports, rawText.lines()) + + if (!importsCorrect) { + report( + CodeSmell( + issue, + testEntity, + "Imports must be ordered in lexicographic order without any empty lines in-between " + + "with \"java\", \"javax\", \"kotlin\" and aliases in the end. This should then be followed by " + + "pre-processed imports.", + ), + ) + } + + super.visitImportList(importList) + } +} diff --git a/detekt/src/main/kotlin/imports/ImportRuleSetProvider.kt b/detekt/src/main/kotlin/imports/ImportRuleSetProvider.kt new file mode 100644 index 000000000000..3b50cf22c2c8 --- /dev/null +++ b/detekt/src/main/kotlin/imports/ImportRuleSetProvider.kt @@ -0,0 +1,17 @@ +package at.hannibal2.skyhanni.detektrules.imports + +import com.google.auto.service.AutoService +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider + +@AutoService(RuleSetProvider::class) +class ImportRuleSetProvider : RuleSetProvider { + override val ruleSetId: String = "ImportRules" + + override fun instance(config: Config): RuleSet { + return RuleSet(ruleSetId, listOf( + CustomImportOrdering(config) + )) + } +} diff --git a/detekt/src/main/kotlin/root.kt b/detekt/src/main/kotlin/root.kt new file mode 100644 index 000000000000..9b95a398f30a --- /dev/null +++ b/detekt/src/main/kotlin/root.kt @@ -0,0 +1 @@ +package at.hannibal2.skyhanni.detektrules diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b31628859ee8..dc79cc80fd38 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,765 @@ # SkyHanni - Change Log +## Version 0.28 (in Beta) + +### New Features + +#### Hoppity Features + ++ Added an easier way to check your unique Hoppity Eggs collected on each island. - martimavocado (https://github.com/hannibal002/SkyHanni/pull/2625) + + Shows your progress in the Warp Menu. + + Can be automatically hidden when an island is complete. ++ Added the ability to block opening the Chocolate Factory when Booster Cookie is inactive. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2713) ++ Added a feature to block opening Hoppity's trade menu from Abiphone calls if you do not have coins in your purse. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2664) ++ Added the ability to prevent closing Meal Eggs that have Rabbit the Fish inside. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2712) + +#### Inventory Features + ++ Added Focus Mode. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2694) + + In Focus Mode, only the name of the item is displayed instead of the full description. + +#### Fishing Features + ++ Added Lava Replacement. - HiZe (https://github.com/hannibal002/SkyHanni/pull/1885) + + Replaces the lava texture with the water texture. + + Primarily used for lava fishing in the Crimson Isle, but can be used anywhere else if the option is enabled. + +#### Mining Features + ++ Added Precision Mining Highlighter. - Cuz_Im_Clicks (https://github.com/hannibal002/SkyHanni/pull/2614) + + Draws a box over the Precision Mining particles. ++ Added highlighting boxes to Crystal Nucleus crystals during Hoppity's Hunt. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2598) ++ Added Flowstate Helper. - martimavocado (https://github.com/hannibal002/SkyHanni/pull/2561) + + Displays stats for the Flowstate enchantment on mining tools. + +#### Dungeon Features + ++ Added Terminal Waypoints. - Stella (https://github.com/hannibal002/SkyHanni/pull/2719) + + Displays waypoints during the F7/M7 Goldor Phase. + +#### Chat Features + ++ Added chat compacting for 'items in stash' warnings. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2639) + +### Improvements + +#### Inventory Improvements + ++ Added Pocket Sack-in-a-Sack support to Estimated Item Value. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2698) + +#### Chat and Command Improvements + ++ Replaced repeated SkyHanni messages with the previous message. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2700) ++ Added support for Guilds in player-related tab completions. - ThatGravyBoat (https://github.com/hannibal002/SkyHanni/pull/2637) ++ Added support for all Guild and Friend commands in tab completions. - ThatGravyBoat (https://github.com/hannibal002/SkyHanni/pull/2637) ++ Reordered commands in categories. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2642) ++ Renamed some commands for clarity. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2642) + +#### Combat Improvements + ++ Added Totem of Corruption and Enrager to the Ability Cooldown feature. - DungeonHub (https://github.com/hannibal002/SkyHanni/pull/2706) + +#### Mining Improvements + ++ Made the "You need a stronger tool to mine ..." chat filter hide every such message, not just Crystal Hollows gemstones. - Luna (https://github.com/hannibal002/SkyHanni/pull/2724) ++ Added an option to draw a line to your golden or diamond goblin. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2717) + +#### Diana Improvements + ++ Added support for detecting and handling Inquisitor spawn messages from other mods from chat. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2720) + +#### Fishing Improvements + ++ Added an option to always display the Barn Fishing Timer anywhere. - NeoNyaa (https://github.com/hannibal002/SkyHanni/pull/2735) + +#### Hoppity Improvements + ++ Improved the Time Tower Usage Warning so it doesn't spam messages. - MTOnline (https://github.com/hannibal002/SkyHanni/pull/2730) + +#### Misc Improvements + ++ Added distance display to waypoints created by Patcher's Send Coords feature. - jani (https://github.com/hannibal002/SkyHanni/pull/2704) ++ Made multiple improvements to the Custom Scoreboard. - Empa, j10a1n15 (https://github.com/hannibal002/SkyHanni/pull/2162) + + Added an option to align the text. + + Improved overall performance. + + Added the Party Leader to the Party Element. + + Separated title and footer alignment. + + Added a custom alpha footer. + +### Fixes + +#### Dungeon Fixes + ++ Fixed Magical Power resetting to 0 when opening "Your Bags" in the Catacombs. - j10a1n15 (https://github.com/hannibal002/SkyHanni/pull/2710) ++ Fixed a rare case where invisible Fels were highlighted even though they shouldn't. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2746) + +#### Fishing Fixes + ++ Fixed fishing displays showing in dungeons. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2697) + +#### Inventory Fixes + ++ Fixed "No Guardian Pet warning" not supporting Pet Rule "On open Experimentation Table". - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2699) ++ Fixed an error with compact experiment rewards chat messages. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2702) ++ Fixed Craft Materials Bazaar not working with long item names. - Fazfoxy (https://github.com/hannibal002/SkyHanni/pull/2703) ++ Fixed the debug feature that allows you to add/remove stars being enabled by default. - CalMWolfs (https://github.com/hannibal002/SkyHanni/pull/2715) ++ Fixed displaying the Guardian warning text in the Experimentation Table even when using a Guardian Pet. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2718) ++ Fixed locked Ultimate enchantments being hidden by Enchant Parsing. - martimavocado (https://github.com/hannibal002/SkyHanni/pull/2732) ++ Fixed Chest Value Display on Carpentry Chests. - fazfoxy (https://github.com/hannibal002/SkyHanni/pull/2743) ++ Fixed Compact Item Stars. - Empa, Fazfoxy (https://github.com/hannibal002/SkyHanni/pull/2741) + +#### Combat Fixes + ++ Fixed Ghost Counter display appearing while in incorrect areas on the map. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2696) ++ Fixed Ashfang Blazes sometimes being highlighted with the wrong color. - Empa (https://github.com/hannibal002/SkyHanni/pull/2112) ++ Fixed Ashfang Reset Cooldown counting in the wrong direction. - Empa (https://github.com/hannibal002/SkyHanni/pull/2112) ++ Fixed Millennia-Aged Blaze not being highlighted by the Area Boss Highlight feature. - jani (https://github.com/hannibal002/SkyHanni/pull/2707) ++ Fixed a small typo in Bestiary Display. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2748) + +#### Custom Scoreboard Fixes + ++ Fixed Custom Scoreboard not showing the Second Barbarian Quest. - j10a1n15 (https://github.com/hannibal002/SkyHanni/pull/2709) + +#### Hoppity Fixes + ++ Fixed the chocolate egg share message sometimes displaying the wrong location name. - martimavocado (https://github.com/hannibal002/SkyHanni/pull/2711) ++ Fixed El Dorado not receiving a compacted chat message. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2742) + +#### Garden Fixes + ++ Fixed farming weight not disappearing when the config option is off. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2731) ++ Fixed New Visitor Ping triggering too late if the player is actively farming. - Luna (https://github.com/hannibal002/SkyHanni/pull/2767) + +#### Crimson Isle Fixes + ++ Fixed Replace Lava not working with OptiFine. - CalMWolfs + nopo (https://github.com/hannibal002/SkyHanni/pull/2727) + +#### Diana Fixes + ++ Fixed Griffin Pet Warning not supporting Diana Autopet rules. - hannibal2 (https://github.com/hannibal002/SkyHanni/pull/2722) + +#### Commands Fixes + ++ Fixed /shtranslate not working in most cases. - Obsidian (https://github.com/hannibal002/SkyHanni/pull/2693) + +#### Mining Fixes + ++ Fixed a crash when attempting to edit the Flowstate Helper config. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2740) + +#### Chocolate Factory Fixes + ++ Fixed an issue where the Time Tower Usage Warning would notify you after expiration or when you have 0 charges. - MTOnline (https://github.com/hannibal002/SkyHanni/pull/2751) + +#### Event Fixes + ++ Fixed Fear Stat Display. - Fazfoxy (https://github.com/hannibal002/SkyHanni/pull/2766) + +#### Misc Fixes + ++ Fixed SkyHanni messages being sent twice. - CalMWolfs (https://github.com/hannibal002/SkyHanni/pull/2736) ++ Fixed the formatting of negative durations. - Empa (https://github.com/hannibal002/SkyHanni/pull/2726) + +### Technical Details + ++ Assigned 'backend' label to PRs with 'backend' in the title. - CalMWolfs (https://github.com/hannibal002/SkyHanni/pull/2690) + + This does not change the functionality of the current bug fix label. ++ Added SuggestionProvider. - ThatGravyBoat (https://github.com/hannibal002/SkyHanni/pull/2637) + + This new class simplifies building suggestion trees. ++ Added Stats API. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2253) ++ Cleaned up LorenzVec and added drawLineToEye. - Empa (https://github.com/hannibal002/SkyHanni/pull/2056) ++ Added SkyHanni event inheritance. - ThatGravyBoat (https://github.com/hannibal002/SkyHanni/pull/2047) ++ Added /shtranslateadvanced command. - Obsidian (https://github.com/hannibal002/SkyHanni/pull/2693) + + Allows specifying both the source and target language. ++ Added changelog verification. - CalMWolfs (https://github.com/hannibal002/SkyHanni/pull/2692) + + This action ensures your PR is in the correct format so that it can be used by the release notes tool. ++ Added dungeon phase detection. - martimavocado (https://github.com/hannibal002/SkyHanni/pull/1865) ++ Added EntityLeaveWorldEvent. - Empa (https://github.com/hannibal002/SkyHanni/pull/2112) ++ Made command registration event-based. - j10a1n15, ThatGravyBoat (https://github.com/hannibal002/SkyHanni/pull/2642) ++ Added "line to the mob" handler. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2717) ++ No longer use NEU GUI elements for the auto-update button. - CalMWolfs (https://github.com/hannibal002/SkyHanni/pull/2725) ++ Added SkyHanni notifications. - CalMWolfs (https://github.com/hannibal002/SkyHanni/pull/2630) ++ Moved Hoppity Warp Menu config to `HoppityWarpMenuConfig`. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2712) ++ Migrated some LorenzEvents without parameters to SkyHanniEvents. - Empa (https://github.com/hannibal002/SkyHanni/pull/2744) ++ Changed SkyHanniEvents without parameters to be objects. - Empa (https://github.com/hannibal002/SkyHanni/pull/2744) + +## Version 0.27 + +### New Features + +#### Garden Features + ++ Added No Pests Chat Filter. - saga (https://github.com/hannibal002/SkyHanni/pull/1957) + + Removed the chat message "There are no Pests on your Garden!". ++ Added No Pests Title. - saga (https://github.com/hannibal002/SkyHanni/pull/1957) + + Shows a title when you use the Pest Tracker without any pests to clear. + +#### Mining Features + ++ Added a "Get from Sack" button in the forge recipe menu to retrieve ingredients. - minhperry (https://github.com/hannibal002/SkyHanni/pull/2106) ++ Added Tracker for Glacite Corpses. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2306) + + Tracks overall loot and loot per type. ++ Hides tooltips of items inside the Fossil Excavator in Glacite Tunnels. - Cuz_Im_Clicks (https://github.com/hannibal002/SkyHanni/pull/2539) + +#### Rift Features + ++ Added Motes per Session. - Empa (https://github.com/hannibal002/SkyHanni/pull/2323) ++ Added Crafting Room Helper. - HiZe (https://github.com/hannibal002/SkyHanni/pull/2178) + + Shows a holographic mob at the location where the mob is present in the real room inside the Mirrorverse in Rift. ++ Added Rift Time Real-Time Nametag Format. - Empa (https://github.com/hannibal002/SkyHanni/pull/2015) ++ Added a helper for tracking the Buttons Enigma Soul in the Rift. - MTOnline (https://github.com/hannibal002/SkyHanni/pull/2616) + + Using pathfinding to guide the player to the nearest spot with unpressed buttons and highlights them. ++ Added a route helper for Gunther's Rift Race in the West Village. - MTOnline (https://github.com/hannibal002/SkyHanni/pull/2616) ++ Added the ability to mute Wilted Berberis sounds when not farming. - MTOnline (https://github.com/hannibal002/SkyHanni/pull/2616) + +#### Dungeon Features + ++ Added highlight for starred dungeon mobs. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/1558) ++ Added highlight for Fel skulls. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/1558) + + Optionally draws a line to them as well. ++ Added a Secret Chime for Dungeons with adjustable pitch and sound. - Ovi_1 (https://github.com/hannibal002/SkyHanni/pull/2478) + + The sound and pitch of chimes in dungeons are customizable. + +#### Scoreboard Features + ++ Added Soulflow to the Custom Scoreboard. - Empa (https://github.com/hannibal002/SkyHanni/pull/1837) + + Requires Soulflow to be enabled in Hypixel settings: /tab -> Profile Widget -> Show Soulflow. ++ Added the current minister to the calendar. - j10a1n15 (https://github.com/hannibal002/SkyHanni/pull/2342) ++ Allowed the use of the Custom Scoreboard outside of SkyBlock, but only on Hypixel. - j10a1n15 (https://github.com/hannibal002/SkyHanni/pull/1881) ++ Added an option to disable custom lines in the Custom Scoreboard. - j10a1n15 (https://github.com/hannibal002/SkyHanni/pull/1881) + +#### Hoppity Features + ++ Added Hoppity Hunt event summary. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2311) + + Use /shhoppitystats for live stats. ++ Added optional warning when Hoppity calls you with a rabbit to sell. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2272) ++ Added hotkey for picking up Abiphone calls from Hoppity. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2272) ++ Added a draggable list for Hoppity's Collection menu rabbit highlighting. - the1divider (https://github.com/hannibal002/SkyHanni/pull/2438) + + Factory/Shop milestones = Yellow/Gold. + + Stray rabbits = Dark Aqua. + + Abi = Dark Green. ++ Added a toggle to highlight found rabbits in Hoppity's Collection menu. - the1divider (https://github.com/hannibal002/SkyHanni/pull/2438) ++ Added the ability to change the color of missing rabbit dyes in Hoppity's Collection to reflect rabbit rarity. - Daveed (https://github.com/hannibal002/SkyHanni/pull/2522) + +#### The Carnival Features + ++ Added a Reminder to claim Carnival Tickets. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2498) ++ Added display for Carnival Goals. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2498) ++ Added a feature to double-click the Carnival NPC to start the game without clicking in the chat. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2498) ++ Added Zombie Shootout QoL improvements for the Carnival. - ILike2WatchMemes (https://github.com/hannibal002/SkyHanni/pull/2497) + + Colored hitboxes. + + Lamp timer + line. + +#### GUI Features + ++ Added Editable XP Bar. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/1944) + + Enabled moving and scaling of the XP bar in the SkyHanni GUI Editor. ++ Added Display Tab Widgets. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/1276) + + Allows the display of information from the Tab (e.g., Bestiary info). + +#### Inventory Features + ++ Added a warning when opening the Experimentation Table without a Guardian pet. - martimavocado (https://github.com/hannibal002/SkyHanni/pull/2127) ++ Added Enchanting Experience as Stack Size in Experimentation Table. - saga (https://github.com/hannibal002/SkyHanni/pull/1988) + + Added to the Item Number list. ++ Show dye hex code as the actual color in the item lore. - nopo (https://github.com/hannibal002/SkyHanni/pull/2321) ++ Added Display for Bits on Cookie buy. - Thunderblade73 (https://github.com/hannibal002/SkyHanni/pull/2265) + + Shows the Bits you would gain. + + Can show the change for the available Bits. + + Also shows the time more clearly (or adds it if not present). ++ Added Compact Experimentation Table chat rewards. - ILike2WatchMemes (https://github.com/hannibal002/SkyHanni/pull/2209) + + Uses a compact chat message of rewards gained from Add-ons/Experiments. ++ Added Personal Compactor/Deletor Overlay. - Empa (https://github.com/hannibal002/SkyHanni/pull/1869) + + Shows what items are currently inside the personal compactor/deletor, and whether the accessory is turned on or off. ++ Added Accessory magical power display as stack size. - minhperry (https://github.com/hannibal002/SkyHanni/pull/2243) + + Only works inside the Accessory Bag and Auction House. ++ Added `/gfs` to fix a broken Piggy Bank. - j10a1n15 (https://github.com/hannibal002/SkyHanni/pull/2150) ++ Added Superpair Display. - ILike2WatchMemes (https://github.com/hannibal002/SkyHanni/pull/2171) + + Displays found and matched pairs, power-ups, and missing pairs/normals. ++ Added Experiments Dry-Streak Display. - ILike2WatchMemes (https://github.com/hannibal002/SkyHanni/pull/2171) + + Shows attempts and XP since the last ULTRA-RARE. ++ Added Experiments Profit Tracker. - ILike2WatchMemes (https://github.com/hannibal002/SkyHanni/pull/2171) + + Tracks profits in Coins and Enchanting XP. ++ Added Ultimate Enchant Star. - Empa (https://github.com/hannibal002/SkyHanni/pull/2612) + + Shows a star on Enchanted Books with an Ultimate Enchant. + +#### Chat Features + ++ Added `/shcolors` command. - minhperry (https://github.com/hannibal002/SkyHanni/pull/2216) + + Prints a list of all Minecraft color and formatting codes in chat. ++ Added Remind command. - ThatGravyBoat & Zickles (https://github.com/hannibal002/SkyHanni/pull/1708) + + Use `/shremind