diff --git a/.gitattributes b/.gitattributes index dfe07704..097f9f98 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,9 @@ -# Auto detect text files and perform LF normalization -* text=auto +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 6a869f79..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: AsoDesu - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behaviour: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behaviour** -A clear and concise description of what you expected to happen. - -**Screenshots & Logs** -If applicable, add screenshots or log snippets to help explain your problem. - -**System & Version Information** -(This can usually be found in the F3 Menu) - - Java Version: [e.g. Java 17] -- Minecraft Version: [e.g. 1.19.4] -- IslandUtils Version: [e.g. 1.4.1] -- OS: [e.g. Windows/MacOS] -- CPU: [e.g. Intel i5-10400F] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7d..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dcac462f..b01da52c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,27 +1,37 @@ +# Automatically build the project and run any configured tests for every push +# and submitted pull request. This can help catch issues that only occur on +# certain platforms or Java versions, and provides a first line of defence +# against bad commits. + name: build -on: - push: - branches: - - 'main' - - 'mc/**' - pull_request: - workflow_dispatch: +on: [pull_request, push] jobs: build: - runs-on: ubuntu-latest + strategy: + matrix: + # Use these Java versions + java: [ + 21, # Current Java LTS + ] + runs-on: ubuntu-22.04 steps: - - name: Checkout project sources - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - java-version: '21' - distribution: 'temurin' - - name: Build with Gradle - uses: gradle/gradle-build-action@749f47bda3e44aa060e82d7b3ef7e40d953bd629 + - name: checkout repository + uses: actions/checkout@v4 + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v2 + - name: setup jdk ${{ matrix.java }} + uses: actions/setup-java@v4 with: - arguments: build - - uses: actions/upload-artifact@v4 + java-version: ${{ matrix.java }} + distribution: 'microsoft' + - name: make gradle wrapper executable + run: chmod +x ./gradlew + - name: build + run: ./gradlew build + - name: capture build artifacts + if: ${{ matrix.java == '21' }} # Only upload artifacts built from latest java + uses: actions/upload-artifact@v4 with: - name: Package - path: build/libs + name: Artifacts + path: build/libs/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index b709d35d..ac36aeeb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,128 +1,43 @@ -# User-specific stuff -.idea/ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# eclipse +*.launch + +# idea + +.idea/ *.iml *.ipr *.iws -# IntelliJ -out/ -# mpeltonen/sbt-idea plugin -.idea_modules/ +# vscode -# Eclipse -*.launch +.settings/ +.vscode/ bin/ .classpath .project -.settings/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -*~ +# macos -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* +*.DS_Store -# KDE directory preferences -.directory +# fabric -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -.gradle -build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Cache of project -.gradletasknamecache - -**/build/ - -# Common working directory run/ -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar +# java + +hs_err_*.log +replay_*.log +*.hprof +*.jfr -*.ogg -.profileconfig.json +# kotlin +.kotlin/ diff --git a/LICENSE b/LICENSE index 763e53e2..dfa9b3a1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2022 +Copyright (c) 2024, AsoDesu_ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 0f51a505..00000000 --- a/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Island Utils - -A client-side utility mod for MCC Island - -**Requires Minecraft 1.20 or higher, [Yet Another Config Lib](https://modrinth.com/mod/yacl) and [Noxesium](https://modrinth.com/mod/noxesium)** - - - - - -#### For issues and support check [issues on GitHub](https://github.com/AsoDesu/IslandUtils/issues) or [join the Discord](https://discord.gg/zU6Hwap4Gv) - ---- - -**This mod is not an official product or endorced by Noxcrew (MCCI) in any way.\ -Game music was created by [Issac Wilkins](https://open.spotify.com/artist/0AhY6cET8JCq1ARiwnTkGi), all credits go to them and Noxcrew.** - -## Utilities -- Event music to the games on Island -- Custom Volume slider for music and sound effects -- Confirmation screen when clicking "Disconnect" during games -- Preview of your character when in the wardrobe or shops -- Discord Presence -- Message to show friends in the same game -- Buttons above the chat box to switch between chat channels -- Split Timer for Parkour Warrior: Dojo -- Notifications for when Items you're crafting are complete -- Command for seeing how long your items have left in the Forge or assembler diff --git a/build.gradle.kts b/build.gradle.kts index 1db0d5cf..e13e3cd6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,85 +1,72 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { - id("fabric-loom") version "1.10-SNAPSHOT" - id("maven-publish") + kotlin("jvm") version "2.3.0" + kotlin("plugin.serialization") version "2.3.0" + id("net.fabricmc.fabric-loom-remap") version "1.14-SNAPSHOT" + java } -version = "${properties["mod_version"]!!}${extraVersion()}" -group = properties["maven_group"]!! -val targetJavaVersion = 21 +group = property("maven_group")!! +version = "${property("mod_version")}${extraVersion()}" repositories { - mavenCentral() - maven("https://maven.shedaniel.me/") - maven("https://jitpack.io") + maven("https://maven.noxcrew.com/public") + maven("https://maven.enginehub.org/repo/") maven("https://maven.terraformersmc.com/releases/") - maven("https://maven.isxander.dev/releases") - maven("https://maven.isxander.dev/snapshots") - maven("https://oss.sonatype.org/content/repositories/snapshots/") - maven(uri("https://maven.noxcrew.com/public")) - maven(uri("https://maven.enginehub.org/repo/")) - exclusiveContent { - forRepository { - maven { - name = "Modrinth" - url = uri("https://api.modrinth.com/maven") - } - } - filter { - includeGroup("maven.modrinth") - } - } + maven("https://api.modrinth.com/maven") + maven("https://maven.fabricmc.net/") } dependencies { - // Minecraft - minecraft("com.mojang:minecraft:${properties["minecraft_version"]!!}") + minecraft("com.mojang:minecraft:${property("minecraft_version")}") mappings(loom.officialMojangMappings()) + modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}") - // Fabric - modImplementation("net.fabricmc:fabric-loader:${properties["loader_version"]!!}") - modImplementation("net.fabricmc.fabric-api:fabric-api:${properties["fabric_version"]!!}") - - // Other mods - modApi("dev.isxander:yet-another-config-lib:${properties["yacl_version"]!!}") { - exclude(group = "com.twelvemonkeys.common") - exclude(group = "com.twelvemonkeys.imageio") - } - modApi("com.terraformersmc:modmenu:${properties["mod_menu_version"]!!}") - modApi("com.noxcrew.noxesium:fabric:${properties["noxesium_version"]!!}") + modImplementation("net.fabricmc:fabric-language-kotlin:${property("fabric_kotlin_version")}") + modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") + modImplementation("com.terraformersmc:modmenu:${property("modmenu_version")}") + modApi("com.noxcrew.noxesium:fabric:${property("noxesium_version")}") - // Other libraries - include("com.github.JnCrMx:discord-game-sdk4j:v0.5.5") - implementation("com.github.JnCrMx:discord-game-sdk4j:v0.5.5") + implementation("io.github.vyfor:kpresence:0.6.6") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") } -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 +loom { + accessWidenerPath = file("src/main/resources/islandutils.accesswidener") } -tasks.jar { - from("LICENSE") { - rename { "${it}_${properties["archives_base_name"]!!}" } +tasks { + processResources { + inputs.property("version", project.version) + filesMatching("fabric.mod.json") { + expand(getProperties()) + expand(mutableMapOf("version" to project.version)) + } } -} -tasks.processResources { - inputs.property("version", project.version) - filteringCharset = "UTF-8" - - filesMatching("fabric.mod.json") { - expand("version" to project.version) + jar { + from("LICENSE") { + rename { "${it}_${project.base.archivesName.get()}" } + } } -} -loom { - accessWidenerPath.set(file("src/main/resources/islandutils.accesswidener")) + compileKotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } + } - mixin { - defaultRefmapName.set("islandutils.refmap.json") + compileJava { + options.release = 21 } } +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + fun extraVersion(): String { val env = System.getenv() return if (env["CI"] == "true") { @@ -87,6 +74,6 @@ fun extraVersion(): String { val runNumber = env["GITHUB_RUN_NUMBER"]!! "-pre+$runNumber-$branch" } else { - "+${properties["minecraft_version"]}" + "+${property("minecraft_version")}" } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bd8fd740..779d6b3f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,18 +3,18 @@ org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true # Fabric Properties -# check these on https://modmuss50.me/fabric.html -minecraft_version=1.21.5 -yarn_mappings=1.21.5+build.1 -loader_version=0.16.13 +# check these on https://fabricmc.net/develop +minecraft_version=1.21.11 +loader_version=0.18.4 +fabric_kotlin_version=1.13.8+kotlin.2.3.0 # Mod Properties -mod_version=1.7.2 -maven_group=net.asodev +mod_version=2.0.0 +maven_group=dev.asodesu.islandutils archives_base_name=islandutils # Dependencies -fabric_version=0.119.9+1.21.5 -yacl_version=3.6.6+1.21.5-fabric -mod_menu_version=14.0.0-rc.2 -noxesium_version=2.7.2 \ No newline at end of file +fabric_version=0.140.2+1.21.11 +noxesium_version=3.0.0-rc.3-SNAPSHOT +modmenu_version=17.0.0-beta.1 +adventure_version=6.8.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a793..23449a2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index f91a4fe7..00000000 --- a/settings.gradle +++ /dev/null @@ -1,9 +0,0 @@ -pluginManagement { - repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - gradlePluginPortal() - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..8b3a62ef --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + maven("https://maven.fabricmc.net/") { + name = "Fabric" + } + mavenCentral() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/src/main/java/dev/asodesu/islandutils/mixin/AddChatChannelButtonsMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/AddChatChannelButtonsMixin.java new file mode 100644 index 00000000..26580eb5 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/AddChatChannelButtonsMixin.java @@ -0,0 +1,68 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import dev.asodesu.islandutils.features.chatbuttons.ChatButtons; +import dev.asodesu.islandutils.features.chatbuttons.button.ChannelButton; +import dev.asodesu.islandutils.features.chatbuttons.button.ChatButtonWidget; +import dev.asodesu.islandutils.options.MiscOptions; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.CommandSuggestions; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.ChatScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(ChatScreen.class) +public abstract class AddChatChannelButtonsMixin extends Screen { + @Shadow protected EditBox input; + @Shadow private CommandSuggestions commandSuggestions; + @Unique private List buttonWidgets = List.of(); + + protected AddChatChannelButtonsMixin(Component component) { + super(component); + } + + @Inject(method = "init", at = @At("TAIL")) + private void init(CallbackInfo ci) { + if (!MinecraftExtKt.isOnline() || !MiscOptions.INSTANCE.getChatChannelButtons().get()) return; + ChatButtons chatButtons = ChatButtons.INSTANCE; + + // (2) - the x position of the edit box background (source: ChatScreen#render - guiGraphics#fill) + int x = 2; + // (this.height - 14) - the y position of the edit box background (source: ChatScreen#render - guiGraphics#fill) + int y = (this.height - 14) - chatButtons.getBUTTON_HEIGHT() - chatButtons.getINPUT_GAP(); + + List widgets = new ArrayList<>(); + for (ChannelButton button : chatButtons.currentButtons()) { + AbstractWidget widget = button.widget(); + widget.setX(x); + widget.setY(y); + + x += widget.getWidth() + chatButtons.getBUTTON_GAP(); + widgets.add(widget); + addWidget(widget); + } + + buttonWidgets = widgets; + } + + @Inject(method = "render", at = @At("TAIL")) + private void render(GuiGraphics guiGraphics, int i, int j, float f, CallbackInfo ci) { + // hide when command suggestions is visible + if (commandSuggestions.isVisible()) return; + for (AbstractWidget widget : buttonWidgets) { + widget.render(guiGraphics, i, j, f); + } + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/BossbarInterceptorMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/BossbarInterceptorMixin.java new file mode 100644 index 00000000..c7795602 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/BossbarInterceptorMixin.java @@ -0,0 +1,29 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.events.bossbar.BossbarEvents; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import net.minecraft.network.chat.Component; +import net.minecraft.world.BossEvent; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.UUID; + +@Mixin(BossEvent.class) +public class BossbarInterceptorMixin { + + @Shadow + @Final + private UUID id; + + @Inject(method = "setName", at = @At("HEAD")) + private void setName(Component component, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + BossbarEvents.INSTANCE.getBOSSBAR_CONTENTS_UPDATE().invoker().onBossbarContents(this.id, component); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/ConfirmDisconnectButtonMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/ConfirmDisconnectButtonMixin.java new file mode 100644 index 00000000..d7cc9fd3 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/ConfirmDisconnectButtonMixin.java @@ -0,0 +1,44 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import dev.asodesu.islandutils.features.ConfirmDisconnectScreen; +import dev.asodesu.islandutils.options.MiscOptions; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.PauseScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; + +@Mixin(PauseScreen.class) +public abstract class ConfirmDisconnectButtonMixin extends Screen { + protected ConfirmDisconnectButtonMixin(Component component) { + super(component); + } + + // god i love mixins + @Redirect( + method = "createPauseMenu", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;" + ), + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;isLocalServer()Z"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/layouts/GridLayout;arrangeElements()V") + ) + ) + private Button.Builder buildButton(Component component, Button.OnPress disconnectOnPress) { + if (!MinecraftExtKt.isOnline() || !MiscOptions.INSTANCE.getConfirmDisconnect().get()) + return Button.builder(component, disconnectOnPress); + + return Button.builder(component, (button -> { + PauseScreen pauseScreen = (PauseScreen)(Object)this; + minecraft.setScreen(new ConfirmDisconnectScreen(pauseScreen, disconnectOnPress)); + })); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/FontLoaderMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/FontLoaderMixin.java new file mode 100644 index 00000000..d8e70fd5 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/FontLoaderMixin.java @@ -0,0 +1,40 @@ +package dev.asodesu.islandutils.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.datafixers.util.Pair; +import dev.asodesu.islandutils.api.chest.font.FontCollection; +import net.minecraft.client.gui.font.FontManager; +import net.minecraft.client.gui.font.providers.GlyphProviderDefinition; +import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.resources.ResourceManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; +import java.util.concurrent.Executor; + +@Mixin(FontManager.class) +public class FontLoaderMixin { + + @Inject( + method = "method_51623", + at = @At( + value = "INVOKE", + target = "Lcom/mojang/datafixers/util/Pair;getFirst()Ljava/lang/Object;", + shift = At.Shift.AFTER + ) + ) + private void inject( + Map.Entry entry, + Identifier fontKey, + ResourceManager resourceManager, + Executor executor, + CallbackInfoReturnable cir, + @Local Pair pair + ) { + FontCollection.INSTANCE.add(fontKey, pair.getSecond().definition()); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/HideBlankSlotsMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/HideBlankSlotsMixin.java new file mode 100644 index 00000000..de31f126 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/HideBlankSlotsMixin.java @@ -0,0 +1,31 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.chest.ItemExtKt; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import dev.asodesu.islandutils.api.game.GameExtKt; +import net.minecraft.resources.Identifier; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Slot.class) +public abstract class HideBlankSlotsMixin { + + @Shadow public abstract ItemStack getItem(); + + @Inject(method = "isHighlightable", at = @At("HEAD"), cancellable = true) + private void isHighlightable(CallbackInfoReturnable cir) { + if (!MinecraftExtKt.isOnline()) return; + if (!GameExtKt.getInLobbyOrFishing()) return; + + ItemStack item = this.getItem(); + Identifier customItemId = ItemExtKt.getCustomItemId(item); + if (customItemId == null) cir.setReturnValue(!item.isEmpty()); + else if (customItemId.getPath().equals("island_interface.generic.blank")) cir.setReturnValue(false); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/ItemTooltipMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/ItemTooltipMixin.java new file mode 100644 index 00000000..acfad3c2 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/ItemTooltipMixin.java @@ -0,0 +1,37 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.extentions.DebugExtKt; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.component.TooltipDisplay; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.function.Consumer; + +@Mixin(ItemStack.class) +public class ItemTooltipMixin { + + @Inject( + method = "addDetailsToTooltip", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/item/TooltipFlag;isAdvanced()Z", + shift = At.Shift.AFTER + ) + ) + private void addDetailsToTooltip(Item.TooltipContext tooltipContext, TooltipDisplay tooltipDisplay, @Nullable Player player, TooltipFlag tooltipFlag, Consumer consumer, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + + ItemStack item = (ItemStack) (Object) this; + DebugExtKt.addDebugTooltip(item, consumer); + } + +} diff --git a/src/main/java/net/asodev/islandutils/mixins/ui/OptionsMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/OptionsButtonMixin.java similarity index 53% rename from src/main/java/net/asodev/islandutils/mixins/ui/OptionsMixin.java rename to src/main/java/dev/asodesu/islandutils/mixin/OptionsButtonMixin.java index 6a2a2123..05897a02 100644 --- a/src/main/java/net/asodev/islandutils/mixins/ui/OptionsMixin.java +++ b/src/main/java/dev/asodesu/islandutils/mixin/OptionsButtonMixin.java @@ -1,6 +1,6 @@ -package net.asodev.islandutils.mixins.ui; +package dev.asodesu.islandutils.mixin; -import net.asodev.islandutils.options.IslandOptions; +import dev.asodesu.islandutils.options.Options; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.options.OptionsScreen; @@ -11,19 +11,19 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(OptionsScreen.class) -public class OptionsMixin extends Screen { +public class OptionsButtonMixin extends Screen { - protected OptionsMixin(Component component) { + protected OptionsButtonMixin(Component component) { super(component); } @Inject(method = "init", at = @At("TAIL")) public void init(CallbackInfo ci) { - if (IslandOptions.getMisc().isEnableConfigButton()) { - this.addRenderableWidget(Button.builder(Component.translatable("menu.island_utils"), (button) -> { - this.minecraft.setScreen(IslandOptions.getScreen(this)); - }).pos(10, 10).size(100, 20).build()); - } + this.addRenderableWidget( + Button.builder(Component.translatable("menu.islandutils"), (btn) -> { + this.minecraft.setScreen(Options.INSTANCE.getScreen(this)); + }).pos(10, 10).size(100, 20).build() + ); } } diff --git a/src/main/java/dev/asodesu/islandutils/mixin/PlayerCloakRenderLayerMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/PlayerCloakRenderLayerMixin.java new file mode 100644 index 00000000..4a650ea2 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/PlayerCloakRenderLayerMixin.java @@ -0,0 +1,26 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.cosmetics.cloak.CloakRenderLayer; +import net.minecraft.client.model.player.PlayerModel; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.client.renderer.entity.player.AvatarRenderer; +import net.minecraft.client.renderer.entity.state.AvatarRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(AvatarRenderer.class) +public abstract class PlayerCloakRenderLayerMixin extends LivingEntityRenderer { + public PlayerCloakRenderLayerMixin(EntityRendererProvider.Context context, PlayerModel entityModel, float f) { + super(context, entityModel, f); + } + + @Inject(method = "", at = @At("TAIL")) + private void init(EntityRendererProvider.Context context, boolean bl, CallbackInfo ci) { + this.addLayer(new CloakRenderLayer(this, context)); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/SidebarInterceptorMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/SidebarInterceptorMixin.java new file mode 100644 index 00000000..f0de80c4 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/SidebarInterceptorMixin.java @@ -0,0 +1,20 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.events.sidebar.SidebarEvents; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import net.minecraft.network.chat.Component; +import net.minecraft.world.scores.Score; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Score.class) +public class SidebarInterceptorMixin { + + @Inject(method = "display(Lnet/minecraft/network/chat/Component;)V", at = @At("TAIL")) + public void display(Component component, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + SidebarEvents.INSTANCE.getLINE_UPDATE().invoker().onSidebarLine(component); + } +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/SoundInterceptorMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/SoundInterceptorMixin.java new file mode 100644 index 00000000..60d66224 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/SoundInterceptorMixin.java @@ -0,0 +1,91 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.events.sound.SoundEvents; +import dev.asodesu.islandutils.api.events.sound.SoundStopCallback; +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo; +import dev.asodesu.islandutils.api.events.sound.SoundPlayCallback; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientCommonPacketListenerImpl; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.multiplayer.CommonListenerCookie; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.network.protocol.game.ClientboundStopSoundPacket; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPacketListener.class) +public abstract class SoundInterceptorMixin extends ClientCommonPacketListenerImpl { + protected SoundInterceptorMixin(Minecraft minecraft, Connection connection, CommonListenerCookie commonListenerCookie) { + super(minecraft, connection, commonListenerCookie); + } + + @Inject( + method = "handleSoundEvent", + at = @At( + value = "INVOKE", // remove potential duplicates + target = "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/network/PacketProcessor;)V", + shift = At.Shift.AFTER + ), + cancellable = true + ) + public void handleSoundEvent(ClientboundSoundPacket clientboundSoundPacket, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + + final SoundInfo[] replacedInfo = {null}; + SoundInfo info = SoundInfo.Companion.fromPacket(clientboundSoundPacket); + SoundPlayCallback.Info callbackInfo = new SoundPlayCallback.Info() { + @Override + public void replace(@NotNull SoundInfo info) { + replacedInfo[0] = info; + } + + @Override + public void cancel() { + ci.cancel(); + } + }; + SoundEvents.getSOUND_PLAY().invoker().onSoundPlay(info, callbackInfo); + + SoundInfo newSoundInfo = replacedInfo[0]; + if (newSoundInfo != null) { + ci.cancel(); + this.minecraft.level.playSeededSound( + this.minecraft.player, + clientboundSoundPacket.getX(), + clientboundSoundPacket.getY(), + clientboundSoundPacket.getZ(), + newSoundInfo.toSoundEvent(), + newSoundInfo.getCategory(), + newSoundInfo.getVolume(), + newSoundInfo.getPitch(), + clientboundSoundPacket.getSeed() + ); + } + } + + @Inject( + method = "handleStopSoundEvent", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/network/PacketProcessor;)V", + shift = At.Shift.AFTER + ), + cancellable = true + ) + public void handleSoundEvent(ClientboundStopSoundPacket clientboundStopSoundPacket, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + + SoundStopCallback.StopInfo info = new SoundStopCallback.StopInfo( + clientboundStopSoundPacket.getName(), + clientboundStopSoundPacket.getSource() + ); + SoundStopCallback.Info callbackInfo = ci::cancel; + SoundEvents.getSOUND_STOP().invoker().onSoundStop(info, callbackInfo); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/TitleInterceptorMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/TitleInterceptorMixin.java new file mode 100644 index 00000000..eec60bc4 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/TitleInterceptorMixin.java @@ -0,0 +1,34 @@ +package dev.asodesu.islandutils.mixin; + +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import dev.asodesu.islandutils.api.game.GameExtKt; +import dev.asodesu.islandutils.features.ClassicHitw; +import dev.asodesu.islandutils.games.HoleInTheWall; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPacketListener.class) +public class TitleInterceptorMixin { + + @Inject( + method = "setSubtitleText", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/network/PacketProcessor;)V", + shift = At.Shift.AFTER + ), + cancellable = true + ) + private void setSubtitleText(ClientboundSetSubtitleTextPacket clientboundSetSubtitleTextPacket, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + if (GameExtKt.getActiveGame() instanceof HoleInTheWall) { + if (ClassicHitw.INSTANCE.handleSubtitle(clientboundSetSubtitleTextPacket.text())) + ci.cancel(); + } + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/chest/ChestMenuInterceptorMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/chest/ChestMenuInterceptorMixin.java new file mode 100644 index 00000000..36337049 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/chest/ChestMenuInterceptorMixin.java @@ -0,0 +1,67 @@ +package dev.asodesu.islandutils.mixin.chest; + +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyser; +import dev.asodesu.islandutils.api.chest.analysis.ContainerScreenHelper; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(AbstractContainerMenu.class) +public class ChestMenuInterceptorMixin { + + @Shadow @Final private static Logger LOGGER; + + @Inject(method = "initializeContents", at = @At("TAIL")) + private void initalizeContents(int stateId, List list, ItemStack itemStack, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + + ChestAnalyser analyser = getScreenChestAnalyser(); + if (analyser == null) return; + + int index = 0; + for (ItemStack newStack : list) { + try { + analyser.analyse(newStack, index); + } catch (Exception e) { + LOGGER.error("Failed to run ChestAnalysis on item", e); + } + index++; + } + } + + @Inject(method = "setItem", at = @At("TAIL")) + private void setItem(int slot, int stateId, ItemStack itemStack, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + + ChestAnalyser analyser = getScreenChestAnalyser(); + if (analyser == null) return; + + analyser.analyse(itemStack, slot); + } + + @Nullable + @Unique + private ChestAnalyser getScreenChestAnalyser() { + Screen screen = Minecraft.getInstance().screen; + if (screen instanceof AbstractContainerScreen containerScreen) { + if (containerScreen.getMenu() != (Object)this) return null; + } + if (!(screen instanceof ContainerScreenHelper helper)) return null; + return helper.getAnalyser(); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/chest/ChestScreenInterceptorMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/chest/ChestScreenInterceptorMixin.java new file mode 100644 index 00000000..eedf48f6 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/chest/ChestScreenInterceptorMixin.java @@ -0,0 +1,123 @@ +package dev.asodesu.islandutils.mixin.chest; + +import dev.asodesu.islandutils.Modules; +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyser; +import dev.asodesu.islandutils.api.chest.analysis.ContainerScreenHelper; +import dev.asodesu.islandutils.api.chest.font.ChestBackgrounds; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; +import java.util.List; + +@Mixin(AbstractContainerScreen.class) +public class ChestScreenInterceptorMixin implements ContainerScreenHelper { + @Shadow @Nullable protected Slot hoveredSlot; + @Shadow protected int imageWidth; + @Shadow protected int imageHeight; + @Unique @Nullable ChestAnalyser analyser; + @Unique Collection menuComponents = List.of(); + + @Inject(method = "", at = @At("TAIL")) + private void init(AbstractContainerMenu abstractContainerMenu, Inventory inventory, Component component, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + + menuComponents = ChestBackgrounds.INSTANCE.get(component); + analyser = Modules.INSTANCE.getChestAnalysis().createAnalyser(menuComponents); + } + + @Inject(method = "render", at = @At("TAIL")) + private void render(GuiGraphics guiGraphics, int i, int j, float f, CallbackInfo ci) { + if (analyser != null) { + analyser.render(guiGraphics, i, j, this); + } + } + + @Inject(method = "tick", at = @At("TAIL")) + private void tick(CallbackInfo ci) { + if (analyser != null) analyser.tick(this); + } + + @Inject(method = "renderSlot", at = @At("HEAD")) + private void renderHead(GuiGraphics guiGraphics, Slot slot, int i, int j, CallbackInfo ci) { + if (analyser != null) analyser.renderSlotBack(guiGraphics, this, slot); + } + + @Inject(method = "renderSlot", at = @At("TAIL")) + private void renderTail(GuiGraphics guiGraphics, Slot slot, int i, int j, CallbackInfo ci) { + if (analyser != null) analyser.renderSlotFront(guiGraphics, this, slot); + } + + @Inject(method = "mouseDragged", at = @At("HEAD")) + private void mouseDragged(MouseButtonEvent mouseButtonEvent, double dragX, double dragY, CallbackInfoReturnable cir) { + if (analyser != null) analyser.mouseDragged(this, mouseButtonEvent.x(), mouseButtonEvent.y(), dragX, dragY); + } + + @Inject(method = "mouseReleased", at = @At("TAIL")) + private void mouseReleased(MouseButtonEvent mouseButtonEvent, CallbackInfoReturnable cir) { + if (analyser != null) analyser.mouseReleased(this, mouseButtonEvent.x(), mouseButtonEvent.y(), mouseButtonEvent.button()); + } + + @Inject(method = "keyPressed", at = @At("TAIL")) + private void keyPressed(KeyEvent keyEvent, CallbackInfoReturnable cir) { + if (analyser != null) analyser.keyPressed(this, keyEvent.key(), keyEvent.scancode(), keyEvent.modifiers()); + } + + @Inject(method = "onClose", at = @At("HEAD")) + private void onClose(CallbackInfo ci) { + if (analyser != null) analyser.close(this); + } + + @Unique + @Override + public @Nullable ChestAnalyser getAnalyser() { + return analyser; + } + + @Unique + @Override + public @NotNull Collection getMenuComponents() { + return menuComponents; + } + + @Unique + @Override + public @Nullable Slot getHoveredSlot() { + return hoveredSlot; + } + + @Unique + @Override + public @NotNull AbstractContainerScreen getScreen() { + return (AbstractContainerScreen)(Object)this; + } + + @Unique + @Override + public int getImageWidth() { + return imageWidth; + } + + @Unique + @Override + public int getImageHeight() { + return imageHeight; + } +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/features/CommandSuggestionHandlerMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/features/CommandSuggestionHandlerMixin.java new file mode 100644 index 00000000..53c05a4d --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/features/CommandSuggestionHandlerMixin.java @@ -0,0 +1,26 @@ +package dev.asodesu.islandutils.mixin.features; + +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import dev.asodesu.islandutils.features.FriendsInGame; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPacketListener.class) +public class CommandSuggestionHandlerMixin { + + @Inject(method = "handleCommandSuggestions", cancellable = true, at = @At("HEAD")) + private void commandSuggestionsResponse(ClientboundCommandSuggestionsPacket clientboundCommandSuggestionsPacket, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + + // check for a FriendsInGame response + if (clientboundCommandSuggestionsPacket.id() == FriendsInGame.TRANSACTION_ID) { + FriendsInGame.INSTANCE.receiveSuggestionCallback(clientboundCommandSuggestionsPacket.suggestions()); + ci.cancel(); + } + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/server/InstanceJoinMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/server/InstanceJoinMixin.java new file mode 100644 index 00000000..6d08b73a --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/server/InstanceJoinMixin.java @@ -0,0 +1,29 @@ +package dev.asodesu.islandutils.mixin.server; + +import dev.asodesu.islandutils.IslandUtils; +import dev.asodesu.islandutils.api.extentions.MinecraftExtKt; +import dev.asodesu.islandutils.api.server.ServerSessionHandler; +import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.network.protocol.game.ClientboundLoginPacket; +import net.minecraft.network.protocol.game.ClientboundRespawnPacket; +import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPacketListener.class) +public class InstanceJoinMixin { + + @Inject(method = "handleLogin", at = @At("RETURN")) + private void handleLogin(ClientboundLoginPacket clientboundLoginPacket, CallbackInfo ci) { + if (!MinecraftExtKt.isOnline()) return; + ServerSessionHandler.INSTANCE.onInstanceJoin(clientboundLoginPacket); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/server/ServerDisconnectMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/server/ServerDisconnectMixin.java new file mode 100644 index 00000000..a5be8a63 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/server/ServerDisconnectMixin.java @@ -0,0 +1,29 @@ +package dev.asodesu.islandutils.mixin.server; + +import dev.asodesu.islandutils.api.server.ServerSessionHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.multiplayer.ClientCommonPacketListenerImpl; +import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.network.Connection; +import net.minecraft.network.DisconnectionDetails; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class ServerDisconnectMixin { + + @Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;ZZ)V", at = @At("TAIL")) + private void disconnect(Screen screen, boolean bl, boolean bl2, CallbackInfo ci) { + ServerSessionHandler.INSTANCE.onDisconnect(); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/server/ServerJoinMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/server/ServerJoinMixin.java new file mode 100644 index 00000000..dcd2a43f --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/server/ServerJoinMixin.java @@ -0,0 +1,28 @@ +package dev.asodesu.islandutils.mixin.server; + +import dev.asodesu.islandutils.api.server.ServerSessionHandler; +import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientHandshakePacketListenerImpl.class) +public class ServerJoinMixin { + + @Shadow + @Final + private @Nullable ServerData serverData; + + @Inject(method = "handleLoginFinished", at = @At("RETURN")) + private void handleLoginFinished(ClientboundLoginFinishedPacket clientboundLoginFinishedPacket, CallbackInfo ci) { + if (serverData == null) return; + ServerSessionHandler.INSTANCE.onConnect(serverData); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/serverlist/JoinMultiplayerTickMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/serverlist/JoinMultiplayerTickMixin.java new file mode 100644 index 00000000..08e85918 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/serverlist/JoinMultiplayerTickMixin.java @@ -0,0 +1,26 @@ +package dev.asodesu.islandutils.mixin.serverlist; + +import dev.asodesu.islandutils.api.notifier.Notifier; +import dev.asodesu.islandutils.api.notifier.ServerListNotificationRenderer; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(JoinMultiplayerScreen.class) +public class JoinMultiplayerTickMixin { + + @Inject(method = "init", at = @At("HEAD")) + private void init(CallbackInfo ci) { + Notifier.INSTANCE.activate(); + } + + @Inject(method = "tick", at = @At("HEAD")) + private void render(CallbackInfo ci) { + Notifier.INSTANCE.tickActive(); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/serverlist/ServerListEntryRenderMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/serverlist/ServerListEntryRenderMixin.java new file mode 100644 index 00000000..f95f815d --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/serverlist/ServerListEntryRenderMixin.java @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.mixin.serverlist; + +import dev.asodesu.islandutils.api.notifier.ServerListNotificationRenderer; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.SelectableEntry; +import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerSelectionList.OnlineServerEntry.class) +public abstract class ServerListEntryRenderMixin extends ServerSelectionList.Entry implements SelectableEntry { + + @Inject(method = "renderContent", at = @At("TAIL")) + private void render(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean bl, float f, CallbackInfo ci) { + ServerListNotificationRenderer.INSTANCE.render( + guiGraphics, + (ServerSelectionList.OnlineServerEntry)(Object)this, + getY(), + getX(), + mouseX, + mouseY + ); + } + +} diff --git a/src/main/java/dev/asodesu/islandutils/mixin/sound/ApplyInjectedSoundsMixin.java b/src/main/java/dev/asodesu/islandutils/mixin/sound/ApplyInjectedSoundsMixin.java new file mode 100644 index 00000000..5a74a0b3 --- /dev/null +++ b/src/main/java/dev/asodesu/islandutils/mixin/sound/ApplyInjectedSoundsMixin.java @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.mixin.sound; + +import dev.asodesu.islandutils.api.music.resources.SoundInjector; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(SoundManager.class) +public class ApplyInjectedSoundsMixin { + + @Inject( + method = "apply(Lnet/minecraft/client/sounds/SoundManager$Preparations;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/sounds/SoundManager$Preparations;apply(Ljava/util/Map;Ljava/util/Map;Lnet/minecraft/client/sounds/SoundEngine;)V", + shift = At.Shift.AFTER + ) + ) + public void applyResourcePackChanges(SoundManager.Preparations preparations, ResourceManager resourceManager, ProfilerFiller profilerFiller, CallbackInfo ci) { + SoundInjector.INSTANCE.apply(); + } + +} diff --git a/src/main/java/net/asodev/islandutils/IslandUtils.java b/src/main/java/net/asodev/islandutils/IslandUtils.java deleted file mode 100644 index e6203925..00000000 --- a/src/main/java/net/asodev/islandutils/IslandUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package net.asodev.islandutils; - -import net.asodev.islandutils.modules.FriendsInGame; -import net.asodev.islandutils.modules.crafting.state.CraftingItems; -import net.asodev.islandutils.modules.crafting.state.CraftingNotifier; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.util.Scheduler; -import net.asodev.islandutils.util.resourcepack.ResourcePackUpdater; -import net.asodev.islandutils.util.updater.UpdateManager; -import net.asodev.islandutils.util.updater.schema.AvailableUpdate; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -import net.fabricmc.loader.api.Version; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Optional; - -public class IslandUtils implements ModInitializer { - private static final Logger logger = LoggerFactory.getLogger(IslandUtils.class); - public static UpdateManager updater; - public static ResourcePackUpdater packUpdater; - - public static Version version; - public static String versionString; - public static AvailableUpdate availableUpdate; - private static boolean isPreRelease = false; - - @Override - public void onInitialize() { - IslandOptions.init(); - FriendsInGame.init(); - - Optional container = FabricLoader.getInstance().getModContainer("islandutils"); - container.ifPresent(modContainer -> { - version = modContainer.getMetadata().getVersion(); - versionString = version.getFriendlyString(); - }); - - updater = new UpdateManager(); - isPreRelease = versionString.contains("-pre") || FabricLoader.getInstance().isDevelopmentEnvironment(); - if (!versionString.contains("-pre")) { - updater.runUpdateCheck(); - } - - try { - CraftingItems.load(); - } catch (Exception e) { - logger.error("Failed to load crafting items", e); - } - new CraftingNotifier(); - - packUpdater = new ResourcePackUpdater(); - packUpdater.get(); - - Scheduler.create(); - } - - public static boolean isPreRelease() { - return isPreRelease; - } -} diff --git a/src/main/java/net/asodev/islandutils/IslandUtilsClient.java b/src/main/java/net/asodev/islandutils/IslandUtilsClient.java deleted file mode 100644 index fe75d97a..00000000 --- a/src/main/java/net/asodev/islandutils/IslandUtilsClient.java +++ /dev/null @@ -1,81 +0,0 @@ -package net.asodev.islandutils; - -import com.mojang.blaze3d.platform.InputConstants; -import net.asodev.islandutils.discord.DiscordPresenceUpdator; -import net.asodev.islandutils.modules.DisguiseKeybind; -import net.asodev.islandutils.modules.NoxesiumIntegration; -import net.asodev.islandutils.modules.music.MusicManager; -import net.asodev.islandutils.modules.plobby.PlobbyFeatures; -import net.asodev.islandutils.modules.plobby.PlobbyJoinCodeCopy; -import net.asodev.islandutils.modules.splits.SplitManager; -import net.asodev.islandutils.modules.splits.ui.SplitUI; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.IslandUtilsCommand; -import net.asodev.islandutils.util.Utils; -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; -import net.minecraft.client.KeyMapping; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import org.lwjgl.glfw.GLFW; - -import java.net.URI; - -@Environment(EnvType.CLIENT) -public class IslandUtilsClient implements ClientModInitializer { - public static KeyMapping openPlobbyKey; - public static KeyMapping disguiseKeyBind; - - @Override - public void onInitializeClient() { - openPlobbyKey = KeyBindingHelper.registerKeyBinding(new KeyMapping( - "key.islandutils.plobbymenu", - InputConstants.Type.KEYSYM, - GLFW.GLFW_KEY_K, - "category.islandutils.keys" - )); - disguiseKeyBind = KeyBindingHelper.registerKeyBinding(new KeyMapping( - "key.islandutils.disguise", - InputConstants.Type.KEYSYM, - GLFW.GLFW_KEY_N, - "category.islandutils.keys" - )); - SplitManager.init(); - DisguiseKeybind.registerDisguiseInput(); - PlobbyFeatures.registerEvents(); - IslandUtilsCommand.register(); - DiscordPresenceUpdator.init(); - PlobbyJoinCodeCopy.register(); - - if (Utils.isLunarClient()) { - SplitUI.setupFallbackRenderer(); - } - new NoxesiumIntegration().init(); - MusicManager.init(); - } - - public static void onJoinMCCI(boolean isProduction) { - System.out.println("Connected to MCCI!"); - if (IslandUtils.availableUpdate != null) { - ChatUtils.send("Hey! Update " + IslandUtils.availableUpdate.title() + " is available for Island Utils!"); - - URI releaseUri = URI.create(IslandUtils.availableUpdate.releaseUrl()); - Style style = Style.EMPTY.withClickEvent(new ClickEvent.OpenUrl(releaseUri)); - Component link = Component.literal(IslandUtils.availableUpdate.releaseUrl()).setStyle(style); - Component text = Component.literal(ChatUtils.translate(ChatUtils.prefix + " Download Here: &f")).append(link); - - ChatUtils.send(text); - } else if (IslandUtils.isPreRelease()) { - ChatUtils.send("&cYou are using a pre-release version of IslandUtils! Expect things to be broken and buggy, and report to #test-feedback!"); - } - - DiscordPresenceUpdator.create(!isProduction); - MccIslandState.setGame(Game.HUB); - IslandUtilsEvents.JOIN_MCCI.invoker().onEvent(); - } -} diff --git a/src/main/java/net/asodev/islandutils/IslandUtilsEvents.java b/src/main/java/net/asodev/islandutils/IslandUtilsEvents.java deleted file mode 100644 index 32d87437..00000000 --- a/src/main/java/net/asodev/islandutils/IslandUtilsEvents.java +++ /dev/null @@ -1,93 +0,0 @@ -package net.asodev.islandutils; - -import com.noxcrew.noxesium.network.clientbound.ClientboundMccGameStatePacket; -import net.asodev.islandutils.state.Game; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; - -import java.util.function.Consumer; - -public class IslandUtilsEvents { - - public static Event GAME_CHANGE = EventFactory.createArrayBacked(GameChangeCallback.class, callbacks -> game -> { - for (GameChangeCallback callback : callbacks) { - callback.onGameChange(game); - } - }); - public static Event GAME_UPDATE = EventFactory.createArrayBacked(GameUpdateCallback.class, callbacks -> game -> { - for (GameUpdateCallback callback : callbacks) { - callback.onGameUpdate(game); - } - }); - public static Event GAME_STATE_CHANGE = EventFactory.createArrayBacked(GameStateChange.class, callbacks -> state -> { - for (GameStateChange callback : callbacks) { - callback.onGameUpdate(state); - } - }); - - public static Event JOIN_MCCI = EventFactory.createArrayBacked(ConnectionCallback.class, callbacks -> () -> { - for (ConnectionCallback callback : callbacks) { - callback.onEvent(); - } - }); - - public static Event QUIT_MCCI = EventFactory.createArrayBacked(ConnectionCallback.class, callbacks -> () -> { - for (ConnectionCallback callback : callbacks) { - callback.onEvent(); - } - }); - - // GAME PACKET EVENTS - public static Event CHAT_MESSAGE = EventFactory.createArrayBacked(ChatMessageEvent.class, callbacks -> (state, modify) -> { - for (ChatMessageEvent callback : callbacks) { - callback.onChatMessage(state, modify); - } - }); - - @FunctionalInterface - public interface GameChangeCallback { - void onGameChange(Game to); - } - - @FunctionalInterface - public interface GameUpdateCallback { - void onGameUpdate(Game to); - } - - @FunctionalInterface - public interface GameStateChange { - void onGameUpdate(ClientboundMccGameStatePacket state); - } - - @FunctionalInterface - public interface ConnectionCallback { - void onEvent(); - } - - @FunctionalInterface - public interface ChatMessageEvent { - void onChatMessage(ClientboundSystemChatPacket state, Modifier modify); - } - - public static class Modifier { - T replacement = null; - boolean shouldCancel = false; - - public void cancel() { - shouldCancel = true; - } - public void replace(T replacement) { - this.replacement = replacement; - } - - public void withCancel(Consumer consumer) { - if (shouldCancel) consumer.accept(shouldCancel); - } - public void withReplacement(Consumer consumer) { - if (replacement != null) consumer.accept(replacement); - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/discord/DiscordPresence.java b/src/main/java/net/asodev/islandutils/discord/DiscordPresence.java deleted file mode 100644 index ba7cd8cb..00000000 --- a/src/main/java/net/asodev/islandutils/discord/DiscordPresence.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.asodev.islandutils.discord; - -import de.jcm.discordgamesdk.Core; -import de.jcm.discordgamesdk.CreateParams; - -import java.io.File; - -public class DiscordPresence { - - static Core core; - static boolean initalised = false; - - static String os = System.getProperty("os.name").toLowerCase(); - - public static boolean init() { - if (initalised) return true; - - File discordLibrary; - try { - discordLibrary = NativeLibrary.grabDiscordNative(); - File discordJNI = NativeLibrary.grabDiscordJNI(); - - if (os.contains("windows")) System.load(discordLibrary.getAbsolutePath()); - System.load(discordJNI.getAbsolutePath()); - } catch (Exception e) { - System.out.println("Failed to grab Natives: " + e.getMessage()); - return false; - } - - Core.initDiscordNative(discordLibrary.getAbsolutePath()); - - CreateParams params = new CreateParams(); - params.setClientID(1027930697417101344L); - params.setFlags(CreateParams.Flags.NO_REQUIRE_DISCORD); - - try { - core = new Core(params); - initalised = true; - } catch (Exception e) { - System.out.println("Failed to Initialise Discord Presence."); - return false; - } - - new Thread(() -> { - while (core != null && core.isOpen()) { - core.runCallbacks(); - try { Thread.sleep(16); } - catch (Exception e) { e.printStackTrace(); } - } - System.out.println("Core has closed. Core: " + core); - }, "IslandUtils - Discord Callbacks").start(); - return true; - } - - public static void clear() { - if (core == null) return; - if (!core.isOpen()) return; - - try { core.close(); } - catch (Exception e) { e.printStackTrace(); } - - initalised = false; - core = null; - } - -} diff --git a/src/main/java/net/asodev/islandutils/discord/DiscordPresenceUpdator.java b/src/main/java/net/asodev/islandutils/discord/DiscordPresenceUpdator.java deleted file mode 100644 index f0dfcfd7..00000000 --- a/src/main/java/net/asodev/islandutils/discord/DiscordPresenceUpdator.java +++ /dev/null @@ -1,240 +0,0 @@ -package net.asodev.islandutils.discord; - -import de.jcm.discordgamesdk.Core; -import de.jcm.discordgamesdk.activity.Activity; -import de.jcm.discordgamesdk.activity.ActivityType; -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.options.categories.DiscordOptions; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import org.jetbrains.annotations.Nullable; - -import java.time.Instant; -import java.util.List; -import java.util.UUID; - -public class DiscordPresenceUpdator { - - // EVERYTHING in this file that interacts with discord is in a try/catch - // Discord GameSDK sometimes likes to not work whenever you ask it to do something - // So we have to be sure we don't crash when that happens. - - // dear contributors: - // i am sorry. - - @Nullable public static Activity activity; - public static UUID timeLeftBossbar = null; - public static Instant started; - private static boolean bigRatMode = false; - - private static List roundGames = List.of(Game.TGTTOS, Game.BATTLE_BOX); - private static List remainGames = List.of(Game.HITW, Game.SKY_BATTLE, Game.DYNABALL, Game.ROCKET_SPLEEF_RUSH); - private static List courseGames = List.of(Game.PARKOUR_WARRIOR_DOJO); - private static List leapGames = List.of(Game.PARKOUR_WARRIOR_SURVIVOR); - - public static void create(boolean enableBigRat) { - bigRatMode = enableBigRat; - if (!IslandOptions.getDiscord().discordPresence) return; - - try { - boolean didInit = DiscordPresence.init(); - if (!didInit) { - System.out.println("Failed to start discord presence"); - return; - } - - activity = new Activity(); - activity.setType(ActivityType.PLAYING); - - activity.assets().setLargeImage("mcci"); - activity.assets().setLargeText("play.mccisland.net"); - - if (started == null) started = Instant.now(); - if (IslandOptions.getDiscord().showTimeElapsed) - activity.timestamps().setStart(started); - - updateActivity(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void init() { - IslandUtilsEvents.GAME_UPDATE.register(DiscordPresenceUpdator::updatePlace); - } - - public static void updateTimeLeft(Long endTimestamp) { - if (activity == null) return; - if (!IslandOptions.getDiscord().showTimeRemaining || !IslandOptions.getDiscord().showGame) return; - - try { - if (endTimestamp != null) activity.timestamps().setEnd(Instant.ofEpochMilli(endTimestamp)); - else activity.timestamps().setEnd(Instant.ofEpochSecond(0)); - } catch (Exception e) { e.printStackTrace(); } - updateActivity(); - } - - public static void updatePlace(Game game) { - if (activity == null) return; - if (!IslandOptions.getDiscord().showGame) return; - - try { - activity.assets().setLargeImage(game.name().toLowerCase()); - activity.assets().setLargeText(game.getName()); - activity.assets().setSmallImage("mcci"); - - if (game == Game.FISHING) { - FishingPresenceUpdator.updateFishingPlace(); - REMAIN_STATE = null; - ROUND_STATE = null; - } else if (game != Game.HUB) - activity.setDetails("Playing " + game.getName()); - else { - activity.setDetails("In the Hub"); - REMAIN_STATE = null; - ROUND_STATE = null; - activity.setState(""); - activity.timestamps().setEnd(Instant.ofEpochSecond(0)); - } - } catch (Exception e) { - e.printStackTrace(); - } - - if (roundGames.contains(game)) - if (ROUND_STATE != null) roundScoreboardUpdate(ROUND_STATE, false); - if (remainGames.contains(game)) - if (REMAIN_STATE != null) remainScoreboardUpdate(REMAIN_STATE, false); - if (courseGames.contains(game)) - if (COURSE_STATE != null) courseScoreboardUpdate(COURSE_STATE, false); - if (leapGames.contains(game)) - if (LEAP_STATE != null) leapScoreboardUpdate(LEAP_STATE, false); - if (game == Game.HUB) { - activity.setState(""); - } - - updateActivity(); - } - - static String REMAIN_STATE; - public static void remainScoreboardUpdate(String value, Boolean set) { - if (activity == null) return; - if (!IslandOptions.getDiscord().showGameInfo || !IslandOptions.getDiscord().showGame) return; - - if (set) REMAIN_STATE = "Remaining: " + value; - if (!remainGames.contains(MccIslandState.getGame())) return; - - try { activity.setState(REMAIN_STATE); } - catch (Exception e) { e.printStackTrace(); } - - updateActivity(); - } - static String ROUND_STATE; - public static void roundScoreboardUpdate(String value, Boolean set) { - if (activity == null) return; - if (!IslandOptions.getDiscord().showGameInfo || !IslandOptions.getDiscord().showGame) return; - - if (set) ROUND_STATE = "Round: " + value; - if (!roundGames.contains(MccIslandState.getGame())) return; - - try { activity.setState(ROUND_STATE); } - catch (Exception e) { e.printStackTrace(); } - - updateActivity(); - } - - static String COURSE_STATE; - public static void courseScoreboardUpdate(String value, Boolean set) { - if (activity == null) return; - if (!IslandOptions.getDiscord().showGameInfo || !IslandOptions.getDiscord().showGame) return; - - if (set) COURSE_STATE = value; - if (!courseGames.contains(MccIslandState.getGame())) return; - - try { activity.setState(COURSE_STATE); } - catch (Exception e) { e.printStackTrace(); } - - updateActivity(); - } - - static String LEAP_STATE; - public static void leapScoreboardUpdate(String value, Boolean set) { - if (activity == null) return; - if (!IslandOptions.getDiscord().showGameInfo || !IslandOptions.getDiscord().showGame) return; - - if (set) LEAP_STATE = "Leap: " + value; - if (!leapGames.contains(MccIslandState.getGame())) return; - - try { activity.setState(LEAP_STATE); } - catch (Exception e) { e.printStackTrace(); } - - updateActivity(); - } - - private static void overrideActivityWithBigRat() { - activity = new Activity(); - activity.setType(ActivityType.PLAYING); - activity.assets().setLargeImage("bigrat"); - activity.assets().setLargeText("BIG RAT"); - activity.setState("BIG RAT BIG RAT BIG RAT"); - activity.setDetails("BIG RAT BIG RAT BIG RAT"); - activity.timestamps().setEnd(Instant.now().plusSeconds(86400)); - } - - public static void updateActivity() { - if (activity == null) return; - if (!IslandOptions.getDiscord().discordPresence) return; - Core core = DiscordPresence.core; - if (core == null || !core.isOpen()) return; - - activity.timestamps().setStart(started); - - if (bigRatMode) { - overrideActivityWithBigRat(); - } - - try { core.activityManager().updateActivity(activity); } - catch (Exception e) { e.printStackTrace(); } - } - - public static void clear() { - DiscordPresence.clear(); - } - - public static void updateFromConfig(DiscordOptions options) { - try { - if (!MccIslandState.isOnline()) { clear(); return; } - - if (options.discordPresence) create(bigRatMode); - else { clear(); return; } - - if (activity == null) activity = new Activity(); - - if (!options.showTimeElapsed) activity.timestamps().setStart(Instant.MAX); - else { - if (started == null) started = Instant.now(); - activity.timestamps().setStart(started); - } - - if (!options.showTimeRemaining) activity.timestamps().setEnd(Instant.ofEpochSecond(0)); - - if (!options.showGame) { - activity.assets().setSmallImage(""); - activity.assets().setSmallText(""); - - activity.assets().setLargeImage("mcci"); - activity.assets().setLargeText("play.mccisland.net"); - - activity.setDetails(""); - activity.setState(""); - activity.timestamps().setEnd(Instant.ofEpochSecond(0)); - } else updatePlace(MccIslandState.getGame()); - - if (!options.showGameInfo) activity.setState(""); - else updatePlace(MccIslandState.getGame()); - - if (!options.discordPresence) clear(); - } catch (Exception e) { e.printStackTrace(); } - } - -} diff --git a/src/main/java/net/asodev/islandutils/discord/FishingPresenceUpdator.java b/src/main/java/net/asodev/islandutils/discord/FishingPresenceUpdator.java deleted file mode 100644 index 6eb4c250..00000000 --- a/src/main/java/net/asodev/islandutils/discord/FishingPresenceUpdator.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.asodev.islandutils.discord; - -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; - -import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static net.asodev.islandutils.discord.DiscordPresenceUpdator.activity; - -public class FishingPresenceUpdator { - public static List temperatures = List.of("temperate", "tropical", "barren"); - public static Map islandNames = new HashMap<>(); - - static { - islandNames.put("temperate_1", "Verdant Woods"); - islandNames.put("temperate_2", "Floral Forest"); - islandNames.put("temperate_3", "Dark Grove"); - islandNames.put("temperate_grotto", "Sunken Swamp"); - - islandNames.put("tropical_1", "Tropical Overgrowth"); - islandNames.put("tropical_2", "Coral Shores"); - islandNames.put("tropical_3", "Twisted Swamp"); - islandNames.put("tropical_grotto", "Mirrored Oasis"); - - islandNames.put("barren_1", "Ancient Sands"); - islandNames.put("barren_2", "Blazing Canyon"); - islandNames.put("barren_3", "Ashen Wastes"); - islandNames.put("barren_grotto", "Volcanic Springs"); - } - - public static void init() { - } - - public static void updateFishingPlace() { - if (activity == null) return; - - String place = islandNames.get(MccIslandState.getSubType()); - if (place != null) { - activity.setDetails("In the " + place); - activity.assets().setLargeImage(MccIslandState.getSubType().toLowerCase()); - activity.assets().setLargeText(place); - - activity.assets().setSmallImage("fishing"); - activity.assets().setSmallText("Fishing"); - } else { - activity.assets().setLargeImage("fishing"); - activity.assets().setLargeText("Fishing"); - } - } -} diff --git a/src/main/java/net/asodev/islandutils/discord/NativeLibrary.java b/src/main/java/net/asodev/islandutils/discord/NativeLibrary.java deleted file mode 100644 index cefbcde2..00000000 --- a/src/main/java/net/asodev/islandutils/discord/NativeLibrary.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.asodev.islandutils.discord; - -import net.asodev.islandutils.util.resourcepack.ResourcePackOptions; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; - -import static net.asodev.islandutils.util.Utils.assertIslandFolder; - -public class NativeLibrary { - - public static String os = System.getProperty("os.name").toLowerCase(); - public static String architecture = System.getProperty("os.arch").toLowerCase(); - - public static File grabDiscordNative() throws Exception { - String name = "discord_game_sdk"; - String extention; - - if (os.contains("windows")) extention = ".dll"; - else if (os.contains("linux")) extention = ".so"; - else if (os.contains("mac os")) extention = ".dylib"; - else { throw new RuntimeException("Can't get OS type: " + os + ". Discord Presence Disabled."); } - - if (architecture.equals("amd64")) architecture = "x86_64"; - - String libIndex = "native/lib/" + architecture + "/" + name+extention; - - assertIslandFolder(); - File outFile = ResourcePackOptions.islandFolder.resolve(libIndex).toFile(); - if (!outFile.exists()) { - System.out.println("Extracting Discord Natives."); - InputStream stream = NativeLibrary.class.getResourceAsStream("/" + libIndex); - if (stream == null) throw new IOException("Natives couldn't be found in the resources. (" + libIndex + ")"); - - outFile.mkdirs(); - Files.copy(stream, outFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - - return outFile; - } - - public static File grabDiscordJNI() throws IOException { - String name = "discord_game_sdk_jni"; - String osName = os; - - String fileName; - - if(osName.contains("windows")) { - osName = "windows"; - fileName = name + ".dll"; - } - else if(osName.contains("linux")) { - osName = "linux"; - fileName = "lib" + name + ".so"; - } - else if(osName.contains("mac os")) { - osName = "macos"; - fileName = "lib" + name + ".dylib"; - } - else { - throw new RuntimeException("cannot determine OS type: "+osName); - } - - if(architecture.equals("x86_64")) - architecture = "amd64"; - - String libIndex = "native/"+osName+"/"+architecture+"/"+fileName; - - File outFile = ResourcePackOptions.islandFolder.resolve(libIndex).toFile(); - if (!outFile.exists()) { - InputStream stream = NativeLibrary.class.getResourceAsStream("/" + libIndex); - if (stream == null) throw new IOException("JNI couldn't be found in the resources. (" + libIndex + ")"); - - outFile.mkdirs(); - Files.copy(stream, outFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - return outFile; - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/BossUIMixin.java b/src/main/java/net/asodev/islandutils/mixins/BossUIMixin.java deleted file mode 100644 index 119164b9..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/BossUIMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.asodev.islandutils.mixins; - -import net.asodev.islandutils.modules.splits.ui.SplitUI; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.BossHealthOverlay; -import net.minecraft.client.gui.components.LerpingBossEvent; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * We're using a mixin rather than HudRenderCallback because - * it has some weird issues with transparency. - *

- * Also SplitUI needs BossBar events list - */ -@Mixin(BossHealthOverlay.class) -public class BossUIMixin { - - @Shadow @Final - Map events; - - @Inject(method = "render", at = @At("HEAD")) - private void render(GuiGraphics guiGraphics, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - - List list = new ArrayList<>(events.values()); - Collections.reverse(list); - int size = list.size(); - for (LerpingBossEvent event : list) { - if (!event.getName().getString().equals("")) break; - size--; - } - SplitUI.renderInstance(guiGraphics, size); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/ItemIDMixin.java b/src/main/java/net/asodev/islandutils/mixins/ItemIDMixin.java deleted file mode 100644 index 7dc9daef..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/ItemIDMixin.java +++ /dev/null @@ -1,80 +0,0 @@ -package net.asodev.islandutils.mixins; - -import com.llamalad7.mixinextras.sugar.Local; -import com.mojang.blaze3d.platform.InputConstants; -import net.asodev.islandutils.mixins.accessors.ContainerScreenAccessor; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.Utils; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.core.component.DataComponentHolder; -import net.minecraft.core.component.DataComponents; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.component.CustomData; -import net.minecraft.world.item.component.TooltipDisplay; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import java.util.List; - -@Mixin(ItemStack.class) -public abstract class ItemIDMixin implements DataComponentHolder { - - @Inject( - method = "getTooltipLines", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/item/ItemStack;addDetailsToTooltip(Lnet/minecraft/world/item/Item$TooltipContext;Lnet/minecraft/world/item/component/TooltipDisplay;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/item/TooltipFlag;Ljava/util/function/Consumer;)V", - shift = At.Shift.AFTER - ) - ) - private void addTooltipLines(Item.TooltipContext tooltipContext, @Nullable Player player, TooltipFlag tooltipFlag, CallbackInfoReturnable> cir, @Local List list) { - if (!MccIslandState.isOnline() || !IslandOptions.getMisc().isDebugMode()) return; - if (!InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), InputConstants.KEY_LCONTROL)) return; - - MutableComponent toAppend = Component.empty(); - if (this.has(DataComponents.CUSTOM_DATA)) tryAddCustomItemID(toAppend); // Append MCCI Custom Item ID - if (Minecraft.getInstance().screen != null) tryAddSlotNumber(toAppend); // Append Slot Number - - if (!toAppend.getSiblings().isEmpty()) { // Add to tooltip lines if not empty - list.add(toAppend); - } - } - - @Unique - private void tryAddCustomItemID(MutableComponent toAppend) { - ResourceLocation customItemID = Utils.getCustomItemID((ItemStack) (Object) this); - if (customItemID == null) return; - toAppend.append(Component.literal(customItemID.toString()).withStyle(ChatFormatting.AQUA)); - } - - @Unique - private void tryAddSlotNumber(MutableComponent toAppend) { - ItemStack me = (ItemStack)(Object)this; - Screen screen = Minecraft.getInstance().screen; - if (!(screen instanceof AbstractContainerScreen containerScreen)) return; - Slot hoveredSlot = ((ContainerScreenAccessor) containerScreen).getHoveredSlot(); - - if (!hoveredSlot.hasItem()) return; - if (hoveredSlot.getItem() != me) return; - Component component = Component.literal(" (" + hoveredSlot.getContainerSlot() + ")").withStyle(ChatFormatting.GRAY); - toAppend.append(component); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/accessors/ContainerScreenAccessor.java b/src/main/java/net/asodev/islandutils/mixins/accessors/ContainerScreenAccessor.java deleted file mode 100644 index 6964e8bf..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/accessors/ContainerScreenAccessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.asodev.islandutils.mixins.accessors; - -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.world.inventory.Slot; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(AbstractContainerScreen.class) -public interface ContainerScreenAccessor { - - @Accessor("hoveredSlot") - Slot getHoveredSlot(); - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/accessors/SoundEngineAccessor.java b/src/main/java/net/asodev/islandutils/mixins/accessors/SoundEngineAccessor.java deleted file mode 100644 index 8562fb12..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/accessors/SoundEngineAccessor.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.asodev.islandutils.mixins.accessors; - -import com.mojang.blaze3d.audio.Listener; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.client.sounds.ChannelAccess; -import net.minecraft.client.sounds.SoundEngine; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -import java.util.Map; - -@Mixin(SoundEngine.class) -public interface SoundEngineAccessor { - - @Accessor("listener") - Listener getListener(); - - @Accessor("instanceToChannel") - Map getInstanceToChannel(); - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/accessors/SoundManagerAccessor.java b/src/main/java/net/asodev/islandutils/mixins/accessors/SoundManagerAccessor.java deleted file mode 100644 index 4a329e58..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/accessors/SoundManagerAccessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.asodev.islandutils.mixins.accessors; - -import net.minecraft.client.sounds.SoundEngine; -import net.minecraft.client.sounds.SoundManager; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(SoundManager.class) -public interface SoundManagerAccessor { - - @Accessor("soundEngine") - SoundEngine getSoundEngine(); - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/accessors/WalkAnimStateAccessor.java b/src/main/java/net/asodev/islandutils/mixins/accessors/WalkAnimStateAccessor.java deleted file mode 100644 index a2a0ac10..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/accessors/WalkAnimStateAccessor.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.asodev.islandutils.mixins.accessors; - -import net.minecraft.world.entity.WalkAnimationState; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(WalkAnimationState.class) -public interface WalkAnimStateAccessor { - - @Accessor("position") - public float getPosition(); - @Accessor("position") - public void setPosition(float position); - - @Accessor("speed") - public void setSpeed(float speed); - @Accessor("speed") - public float getSpeed(); - - @Accessor("speedOld") - public void setSpeedOld(float speedOld); - @Accessor("speedOld") - public float getSpeedOld(); - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/cosmetics/ChestScreenMixin.java b/src/main/java/net/asodev/islandutils/mixins/cosmetics/ChestScreenMixin.java deleted file mode 100644 index 79708cfc..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/cosmetics/ChestScreenMixin.java +++ /dev/null @@ -1,190 +0,0 @@ -package net.asodev.islandutils.mixins.cosmetics; - -import com.mojang.blaze3d.platform.InputConstants; -import net.asodev.islandutils.modules.cosmetics.Cosmetic; -import net.asodev.islandutils.modules.cosmetics.CosmeticSlot; -import net.asodev.islandutils.modules.cosmetics.CosmeticState; -import net.asodev.islandutils.modules.cosmetics.CosmeticType; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.IslandSoundEvents; -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.resources.sounds.SimpleSoundInstance; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ClickType; -import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.List; - -import static net.asodev.islandutils.util.Utils.customModelData; - - -@Mixin(AbstractContainerScreen.class) -public abstract class ChestScreenMixin extends Screen { - @Unique private static final ResourceLocation PREVIEW = ResourceLocation.fromNamespaceAndPath("island", "preview"); - - @Shadow protected Slot hoveredSlot; - - @Shadow @Final protected AbstractContainerMenu menu; - - protected ChestScreenMixin(Component component) { - super(component); - } - - @Inject(method = "", at = @At("TAIL")) - private void onInit(AbstractContainerMenu abstractContainerMenu, Inventory inventory, Component component, CallbackInfo ci) { - LocalPlayer player = Minecraft.getInstance().player; - CosmeticState.hatSlot.setOriginal(new CosmeticSlot(player.getInventory().getItem(EquipmentSlot.HEAD.getIndex(36)))); - CosmeticState.accessorySlot.setOriginal(new CosmeticSlot(player.getInventory().getItem(40))); - } - - @Inject(method = "renderSlot", at = @At("TAIL")) - private void renderSlot(GuiGraphics guiGraphics, Slot slot, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - if (!slot.hasItem()) return; - - boolean shouldRender = false; - - if (CosmeticState.hatSlot.preview != null && CosmeticState.hatSlot.preview.matchesSlot(slot)) shouldRender = true; - else if (CosmeticState.accessorySlot.preview != null && CosmeticState.accessorySlot.preview.matchesSlot(slot)) shouldRender = true; - else if (CosmeticState.mainHandSlot.preview != null && CosmeticState.mainHandSlot.preview.matchesSlot(slot)) shouldRender = true; - - guiGraphics.pose().pushPose(); - if (shouldRender) { - guiGraphics.pose().translate(0.0f, 0.0f, 105f); - guiGraphics.blitSprite(RenderType::guiTextured, PREVIEW, slot.x-3, slot.y-4, 22, 24); - } - guiGraphics.pose().popPose(); - } - - @Inject(method = "render", at = @At("TAIL")) - private void render(GuiGraphics guiGraphics, int i, int j, float f, CallbackInfo ci) { - if (hoveredSlot != null && hoveredSlot.hasItem()) { - ItemStack hoveredItem = hoveredSlot.getItem(); - if (IslandOptions.getCosmetics().isShowOnHover()) { - CosmeticType newType = setHovered(hoveredItem); - if (newType != CosmeticType.HAT) CosmeticState.hatSlot.hover = null; - if (newType != CosmeticType.ACCESSORY) CosmeticState.accessorySlot.hover = null; - if (newType != CosmeticType.MAIN_HAND) CosmeticState.mainHandSlot.hover = null; - } - - if (CosmeticState.isColoredItem(hoveredItem)) { - Integer color = CosmeticState.getColor(hoveredSlot.getItem()); - if (color != null) { - CosmeticState.hoveredColor = color; - return; - } - } - } else { - CosmeticState.hatSlot.hover = null; - CosmeticState.accessorySlot.hover = null; - CosmeticState.mainHandSlot.hover = null; - } - CosmeticState.hoveredColor = null; - } - - private CosmeticType setHovered(ItemStack item) { - CosmeticType type = CosmeticState.getType(item); - if (type == null) return null; - Cosmetic cosmeticByType = CosmeticState.getCosmeticByType(type); - if (cosmeticByType == null) return null; - cosmeticByType.hover = new CosmeticSlot(item, hoveredSlot); - return type; - } - - @Inject(method = "mouseDragged", at = @At("HEAD")) - private void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY, CallbackInfoReturnable cir) { - if (!MccIslandState.isOnline()) return; - - CosmeticState.yRot = CosmeticState.yRot - Double.valueOf(deltaX).floatValue(); - CosmeticState.xRot = CosmeticState.xRot - Double.valueOf(deltaY).floatValue(); - } - - @Inject(method = "keyPressed", at = @At("HEAD")) - private void keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) { - triggerPreviewClicked(keyCode); - } - @Inject(method = "mouseReleased", at = @At("HEAD")) - private void mouseReleased(double d, double e, int keyCode, CallbackInfoReturnable cir) { - triggerPreviewClicked(keyCode); - } - - @Inject(method = "slotClicked", at = @At("HEAD")) - private void slotClicked(Slot slot, int i, int j, ClickType clickType, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - if (slot == null || !slot.hasItem()) return; - ItemStack stack = slot.getItem(); - if (CosmeticState.canBeEquipped(stack)) { - CosmeticType type = CosmeticState.getType(stack); - if (type == null) return; - Cosmetic cosmeticByType = CosmeticState.getCosmeticByType(type); - if (cosmeticByType == null) return; - - cosmeticByType.setOriginal(new CosmeticSlot(stack.copy())); - } - } - - private void triggerPreviewClicked(int keyCode) { - if (!MccIslandState.isOnline()) return; - if (!IslandOptions.getCosmetics().isShowPlayerPreview()) return; - if (hoveredSlot == null || !hoveredSlot.hasItem()) return; - InputConstants.Key previewBind = KeyBindingHelper.getBoundKeyOf(minecraft.options.keyPickItem); - if (keyCode == previewBind.getValue()) { - ItemStack item = hoveredSlot.getItem(); - if (item.is(Items.GHAST_TEAR) || item.is(Items.AIR)) return; - setPreview(item); - } - } - - - @Unique - private void setPreview(ItemStack item) { - CosmeticType type = CosmeticState.getType(item); - float hoverCMD = customModelData(item); - if (type == CosmeticType.HAT) setOrNotSet(CosmeticState.hatSlot, hoverCMD); - else if (type == CosmeticType.ACCESSORY) setOrNotSet(CosmeticState.accessorySlot, hoverCMD); - else if (type == CosmeticType.MAIN_HAND) setOrNotSet(CosmeticState.mainHandSlot, hoverCMD); - } - - @Unique - private void setOrNotSet(Cosmetic cosmetic, float itemCMD) { - if (cosmetic.preview == null || itemCMD != customModelData(cosmetic.preview.item)) - cosmetic.preview = new CosmeticSlot(hoveredSlot); - else - cosmetic.preview = null; - this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(IslandSoundEvents.UI_CLICK_NORMAL, 1f, 1f)); - } - - @Inject(method = "onClose", at = @At("TAIL")) - private void onClose(CallbackInfo ci) { - CosmeticState.hatSlot = new Cosmetic(CosmeticType.HAT); - CosmeticState.accessorySlot = new Cosmetic(CosmeticType.ACCESSORY); - CosmeticState.mainHandSlot = new Cosmetic(CosmeticType.MAIN_HAND); - - CosmeticState.inspectingPlayer = null; - - CosmeticState.yRot = 155; - CosmeticState.xRot = -5; - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/cosmetics/FontLoaderMixin.java b/src/main/java/net/asodev/islandutils/mixins/cosmetics/FontLoaderMixin.java deleted file mode 100644 index c80e8440..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/cosmetics/FontLoaderMixin.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.asodev.islandutils.mixins.cosmetics; - -import net.asodev.islandutils.modules.cosmetics.CosmeticState; -import net.asodev.islandutils.modules.crafting.CraftingUI; -import net.asodev.islandutils.modules.scavenging.Scavenging; -import net.asodev.islandutils.modules.splits.LevelTimer; -import net.minecraft.client.gui.font.providers.BitmapProvider; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import static net.asodev.islandutils.modules.cosmetics.CosmeticState.MCC_ICONS; - -@Mixin(BitmapProvider.Definition.class) -public class FontLoaderMixin { - - @Inject(method = "", at = @At("TAIL")) - private void init(ResourceLocation file, int i, int j, int[][] chars, CallbackInfo ci) { - if (chars.length == 1) { - int[] c = chars[0]; - - StringBuilder builder = new StringBuilder(); - for (int point : c) { builder.appendCodePoint(point); } - - String character = builder.toString(); - Component comp = Component.literal(character).setStyle(Style.EMPTY.withFont(MCC_ICONS)); - switch (file.getPath()) { - case "_fonts/body/blueprint_assembly.png" -> CraftingUI.setAssemblerCharacter(character); - case "_fonts/body/fusion_forge.png" -> CraftingUI.setForgeCharacter(character); - case "_fonts/body/scavenging.png" -> Scavenging.setTitleCharacter(character); - case "_fonts/material_dust.png" -> Scavenging.setDustCharacter(character); - case "_fonts/silver.png" -> Scavenging.setSilverCharacter(character); - case "_fonts/coin_small.png" -> Scavenging.setCoinCharacter(character); - - case "_fonts/tooltips/hat.png" -> CosmeticState.HAT_COMP = comp; - case "_fonts/tooltips/accessory.png" -> CosmeticState.ACCESSORY_COMP = comp; - case "_fonts/tooltips/hair.png" -> CosmeticState.HAIR_COMP = comp; - case "_fonts/medals.png" -> LevelTimer.medalCharacter = character; - case "_fonts/split_up.png" -> LevelTimer.splitUpComponent = comp; - case "_fonts/split_down.png" -> LevelTimer.splitDownComponent = comp; - } - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/cosmetics/PreviewTutorialMixin.java b/src/main/java/net/asodev/islandutils/mixins/cosmetics/PreviewTutorialMixin.java deleted file mode 100644 index 78977ccb..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/cosmetics/PreviewTutorialMixin.java +++ /dev/null @@ -1,59 +0,0 @@ -package net.asodev.islandutils.mixins.cosmetics; - -import com.llamalad7.mixinextras.sugar.Local; -import net.asodev.islandutils.modules.cosmetics.CosmeticState; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.util.ChatUtils; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.component.TooltipDisplay; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import java.util.List; -import java.util.function.Consumer; - -import static net.asodev.islandutils.util.ChatUtils.iconsFontStyle; - -@Mixin(ItemStack.class) -public class PreviewTutorialMixin { - @Unique - private static final Style style0 = Style.EMPTY.withColor(ChatFormatting.DARK_GRAY).withFont(Style.DEFAULT_FONT); - @Unique - private static final Style style1 = Style.EMPTY.withColor(ChatUtils.parseColor("#e9d282")).withFont(Style.DEFAULT_FONT); - @Unique - private static final Style style2 = Style.EMPTY.withColor(ChatUtils.parseColor("#fbe460")).withFont(Style.DEFAULT_FONT); - - @Unique - private static final Component previewComponent = Component.empty() - .append(Component.literal("\ue005").setStyle(iconsFontStyle)) - .append(Component.literal(" > ").setStyle(style0)) - .append(Component.keybind("key.pickItem").append(" to ").setStyle(style1)) - .append(Component.literal("Preview on player").setStyle(style2)); - - @Inject( - method = "addDetailsToTooltip", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/world/item/ItemStack;addAttributeTooltips(Ljava/util/function/Consumer;Lnet/minecraft/world/item/component/TooltipDisplay;Lnet/minecraft/world/entity/player/Player;)V", - shift = At.Shift.BEFORE - ) - ) - private void injectedTooltipLines(Item.TooltipContext tooltipContext, TooltipDisplay tooltipDisplay, @Nullable Player player, TooltipFlag tooltipFlag, Consumer consumer, CallbackInfo ci) { - if (CosmeticState.getType((ItemStack) (Object) this) == null) return; - if (!IslandOptions.getCosmetics().isShowPlayerPreview()) return; - consumer.accept(previewComponent); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/cosmetics/UIMixin.java b/src/main/java/net/asodev/islandutils/mixins/cosmetics/UIMixin.java deleted file mode 100644 index 04114efb..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/cosmetics/UIMixin.java +++ /dev/null @@ -1,127 +0,0 @@ -package net.asodev.islandutils.mixins.cosmetics; - -import net.asodev.islandutils.mixins.accessors.WalkAnimStateAccessor; -import net.asodev.islandutils.modules.cosmetics.CosmeticState; -import net.asodev.islandutils.modules.cosmetics.CosmeticType; -import net.asodev.islandutils.modules.cosmetics.CosmeticUI; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.options.categories.CosmeticsOptions; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.gui.screens.inventory.ContainerScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.item.ItemStack; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import static net.asodev.islandutils.modules.cosmetics.CosmeticState.applyColor; - -@Mixin(ContainerScreen.class) -public abstract class UIMixin extends AbstractContainerScreen { - public UIMixin(ChestMenu abstractContainerMenu, Inventory inventory, Component component) { - super(abstractContainerMenu, inventory, component); - } - - @Inject(method = "renderBg", at = @At("TAIL")) - public void renderBg(GuiGraphics guiGraphics, float f, int i, int j, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - - CosmeticsOptions options = IslandOptions.getCosmetics(); - if (!options.isShowPlayerPreview()) return; - boolean isCosmeticMenu = false; - if (options.isShowOnOnlyCosmeticMenus() && !(isCosmeticMenu = CosmeticState.isCosmeticMenu(this))) return; - - checkInspect(); - - Player player = CosmeticState.getInspectingPlayer(); - Player localPlayer = Minecraft.getInstance().player; - if (player == null) return; - if (localPlayer == null) return; - - ItemStack defaultMainSlot = null; - ItemStack defaultHatSlot = null; - ItemStack defaultAccSlot = null; - - ItemStack hatSlot; - ItemStack accSlot; - - Inventory playerInventory = player.getInventory(); - if (CosmeticState.inspectingPlayer == null || CosmeticState.inspectingPlayer.getUUID() == Minecraft.getInstance().player.getUUID()) { - hatSlot = CosmeticState.hatSlot.getContents().getItem(this.menu); - accSlot = CosmeticState.accessorySlot.getContents().getItem(this.menu); - ItemStack mainSlot = CosmeticState.mainHandSlot.getContents().getItem(this.menu); - if (isCosmeticMenu) { - applyColor(hatSlot); - applyColor(accSlot); - - defaultHatSlot = CosmeticType.HAT.getItem(playerInventory); - playerInventory.setItem(CosmeticType.HAT.getIndex(), hatSlot); - - defaultAccSlot = CosmeticType.ACCESSORY.getItem(playerInventory); - playerInventory.setItem(CosmeticType.ACCESSORY.getIndex(), accSlot); - - defaultMainSlot = player.getMainHandItem(); - player.setItemSlot(EquipmentSlot.MAINHAND, mainSlot); - } - } else { - hatSlot = CosmeticType.HAT.getItem(playerInventory); - accSlot = CosmeticType.ACCESSORY.getItem(playerInventory); - } - - WalkAnimStateAccessor walkAnim = (WalkAnimStateAccessor) player.walkAnimation; - float animPos = walkAnim.getPosition(); - float animSpeed = walkAnim.getSpeed(); - float animSpeedOld = walkAnim.getSpeedOld(); - float attackAnim = player.attackAnim; - - walkAnim.setPosition(0f); - walkAnim.setSpeed(0f); - walkAnim.setSpeedOld(0f); - player.attackAnim = 0; - - int size = Double.valueOf(Math.ceil(this.imageHeight / 2.5)).intValue(); - int x = (this.width - this.imageWidth) / 4; - int y = (this.height / 2) + size; - - CosmeticUI.renderPlayerInInventory( - guiGraphics, - x, // x - y, // y - size , // size - player); // Entity - - walkAnim.setPosition(animPos); - walkAnim.setSpeed(animSpeed); - walkAnim.setSpeedOld(animSpeedOld); - player.attackAnim = attackAnim; - if (defaultHatSlot != null) playerInventory.setItem(CosmeticType.HAT.getIndex(), defaultHatSlot); - if (defaultAccSlot != null) playerInventory.setItem(CosmeticType.ACCESSORY.getIndex(), defaultAccSlot); - if (defaultMainSlot != null) player.setItemSlot(EquipmentSlot.MAINHAND, defaultMainSlot); - - // this code is so ugly omfg - int itemPos = x+(size / 2) - 18; - - y += 8; - int backgroundColor = 0x60000000; - guiGraphics.fill(x-(size / 2) - 2, y, x+(size / 2)+2, y + 19, backgroundColor); - guiGraphics.drawString(this.font, CosmeticState.HAT_COMP, x-(size / 2) + 4, y + 6, 16777215 | 255 << 24); - guiGraphics.renderItem(this.minecraft.player, hatSlot, itemPos, y+2, x + y * this.imageWidth); - - y += 19 + 4; - guiGraphics.fill(x-(size / 2) - 2, y, x+(size / 2)+2, y + 19, backgroundColor); - guiGraphics.drawString(this.font, CosmeticState.ACCESSORY_COMP, x-(size / 2) + 4, y + 6, 16777215 | 255 << 24); - guiGraphics.renderItem(this.minecraft.player, accSlot, itemPos, y+2, x + y * this.imageWidth); - } - - private void checkInspect() { - // TODO: Readd inspect menu support - } -} diff --git a/src/main/java/net/asodev/islandutils/mixins/crafting/CraftingPacketMixin.java b/src/main/java/net/asodev/islandutils/mixins/crafting/CraftingPacketMixin.java deleted file mode 100644 index 9fdb3d0e..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/crafting/CraftingPacketMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.asodev.islandutils.mixins.crafting; - -import net.asodev.islandutils.modules.crafting.CraftingMenuType; -import net.asodev.islandutils.modules.crafting.CraftingUI; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.inventory.ContainerScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.item.ItemStack; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.List; - -@Mixin(AbstractContainerMenu.class) -public abstract class CraftingPacketMixin { - - public CraftingPacketMixin() { - - } - - @Inject(method = "initializeContents", at = @At("TAIL")) - private void contents(int i, List list, ItemStack itemStack, CallbackInfo ci) { - int index = 0; - for (ItemStack stack : list) { - injectedSetItem(index, i, stack, ci); - index++; - } - } - - @Inject(method = "setItem", at = @At("TAIL")) - private void injectedSetItem(int slot, int j, ItemStack item, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - Minecraft minecraft = Minecraft.getInstance(); - if (minecraft.screen instanceof ContainerScreen container) { - Component uiTitle = container.getTitle(); - CraftingMenuType type = CraftingUI.craftingMenuType(uiTitle); - if (type == null) return; - - CraftingUI.analyseCraftingItem(type, item, slot); - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/crafting/RemnantHighlightMixin.java b/src/main/java/net/asodev/islandutils/mixins/crafting/RemnantHighlightMixin.java deleted file mode 100644 index 6c6dbe1d..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/crafting/RemnantHighlightMixin.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.asodev.islandutils.mixins.crafting; - -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.Utils; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.network.chat.Component; -import net.minecraft.world.inventory.Slot; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.List; - -@Mixin(AbstractContainerScreen.class) -public class RemnantHighlightMixin { - - @Inject(method = "renderSlot", at = @At("HEAD")) - private void renderSlot(GuiGraphics guiGraphics, Slot slot, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - if (!slot.hasItem()) return; - - List lore = Utils.getLores(slot.getItem()); - if (lore == null) return; - boolean isRemnant = lore.stream().anyMatch(c -> c.getString().contains("This item is the remnant of an item")); - if (!isRemnant) return; - - int x = slot.x; - int y = slot.y; - guiGraphics.pose().pushPose(); - guiGraphics.pose().translate(0.0f, 0.0f, 105.0f); - guiGraphics.fill(RenderType.gui(), x, y, x + 16, y + 16, 0, 9257316); - guiGraphics.pose().popPose(); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/crafting/ScavengingScreenMixin.java b/src/main/java/net/asodev/islandutils/mixins/crafting/ScavengingScreenMixin.java deleted file mode 100644 index 46ba0941..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/crafting/ScavengingScreenMixin.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.asodev.islandutils.mixins.crafting; - -import net.asodev.islandutils.modules.scavenging.Scavenging; -import net.asodev.islandutils.modules.scavenging.ScavengingTotalList; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.gui.screens.inventory.ContainerScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.inventory.ChestMenu; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(ContainerScreen.class) -public abstract class ScavengingScreenMixin extends AbstractContainerScreen { - public ScavengingScreenMixin(ChestMenu abstractContainerMenu, Inventory inventory, Component component) { - super(abstractContainerMenu, inventory, component); - } - - @Inject(method = "renderBg", at = @At("TAIL")) - public void renderBg(GuiGraphics guiGraphics, float f, int i, int j, CallbackInfo ci) { - if (!MccIslandState.isOnline() || !Scavenging.isScavengingMenuOrDisabled(this)) return; - - ScavengingTotalList silverTotal = Scavenging.getSilverTotal(this.menu); - Scavenging.renderSilverTotal(silverTotal, guiGraphics); - } -} diff --git a/src/main/java/net/asodev/islandutils/mixins/discord/ConnectionMixin.java b/src/main/java/net/asodev/islandutils/mixins/discord/ConnectionMixin.java deleted file mode 100644 index 7b5da951..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/discord/ConnectionMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.asodev.islandutils.mixins.discord; - -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.discord.DiscordPresenceUpdator; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.network.Connection; -import net.minecraft.network.PacketListener; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -@Mixin(Connection.class) -public abstract class ConnectionMixin { - - @Shadow public abstract SocketAddress getRemoteAddress(); - - @Shadow @Nullable private volatile PacketListener packetListener; - - @Inject(method = "disconnect(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD")) - private void disconnect(Component component, CallbackInfo ci) { - SocketAddress remoteAddress = getRemoteAddress(); - if (remoteAddress == null) return; - if (remoteAddress instanceof InetSocketAddress socketAddress) { - String hostName = socketAddress.getHostName(); - if (hostName == null) return; - Minecraft minecraft = Minecraft.getInstance(); - if (hostName.contains("mccisland.net") && packetListener == minecraft.getConnection()) { - ChatUtils.debug("Disconnected from MCC Island."); - DiscordPresenceUpdator.started = null; - DiscordPresenceUpdator.clear(); - IslandUtilsEvents.QUIT_MCCI.invoker().onEvent(); - MccIslandState.setGame(Game.HUB); - } - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/discord/JoinMCCIMixin.java b/src/main/java/net/asodev/islandutils/mixins/discord/JoinMCCIMixin.java deleted file mode 100644 index d647817e..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/discord/JoinMCCIMixin.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.asodev.islandutils.mixins.discord; - -import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; -import net.minecraft.client.multiplayer.ServerData; -import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import static net.asodev.islandutils.IslandUtilsClient.onJoinMCCI; -import static net.asodev.islandutils.util.Utils.isProdMCCI; - -@Mixin(ClientHandshakePacketListenerImpl.class) -public class JoinMCCIMixin { - @Shadow @Final private @Nullable ServerData serverData; - - @Inject(method = "handleLoginFinished", at = @At("HEAD")) - private void handleGameProfile(ClientboundLoginFinishedPacket clientboundLoginFinishedPacket, CallbackInfo ci) { - if (this.serverData == null) return; - if (!this.serverData.ip.toLowerCase().contains("mccisland")) return; - - onJoinMCCI(isProdMCCI(this.serverData.ip.toLowerCase())); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/discord/ScoreDisplayMixin.java b/src/main/java/net/asodev/islandutils/mixins/discord/ScoreDisplayMixin.java deleted file mode 100644 index 4c8552de..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/discord/ScoreDisplayMixin.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.asodev.islandutils.mixins.discord; - -import net.asodev.islandutils.discord.DiscordPresenceUpdator; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.network.chat.Component; -import net.minecraft.world.scores.Score; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -@Mixin(Score.class) -public class ScoreDisplayMixin { - - @Inject(method = "display(Lnet/minecraft/network/chat/Component;)V", at = @At("TAIL")) - public void setScoreDisplay(Component component, CallbackInfo ci) { - String playerPrefix = component.getString(); - - final Map scoreboardPatterns = Map.of( - "REMAIN", Pattern.compile("PLAYERS REMAINING: (?([0-9]*/[0-9]*))"), - "ROUND", Pattern.compile("ROUNDS \\[(?([1-9]*/[1-9]*))]"), - "MAP", Pattern.compile("MAP: (?\\w+(?:,? \\w+)*)"), - "MODIFIER", Pattern.compile("MODIFIER: (?\\w+(?:,? \\w+)*)"), - "COURSE", Pattern.compile("COURSE: (?.*)"), - "LEAP", Pattern.compile("LEAP \\[(?.*/.*)]") - - ); - - for (Map.Entry entry : scoreboardPatterns.entrySet()) { - Matcher matcher = entry.getValue().matcher(playerPrefix); - if (!matcher.find()) continue; - String value = matcher.group(1); - - switch (entry.getKey()) { - case "REMAIN" -> DiscordPresenceUpdator.remainScoreboardUpdate(value, true); - case "ROUND" -> DiscordPresenceUpdator.roundScoreboardUpdate(value, true); - case "MAP" -> MccIslandState.setMap(value.toUpperCase()); // Set our MAP - case "MODIFIER" -> MccIslandState.setModifier(value.toUpperCase()); // Set our MODIFIER - case "COURSE" -> { - DiscordPresenceUpdator.courseScoreboardUpdate(value, true); - MccIslandState.setMap(value); - } - case "LEAP" -> DiscordPresenceUpdator.leapScoreboardUpdate(value, true); - } - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/network/PacketListenerMixin.java b/src/main/java/net/asodev/islandutils/mixins/network/PacketListenerMixin.java deleted file mode 100644 index f372d9c5..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/network/PacketListenerMixin.java +++ /dev/null @@ -1,200 +0,0 @@ -package net.asodev.islandutils.mixins.network; - -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.asodev.islandutils.IslandUtilsClient; -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.discord.DiscordPresenceUpdator; -import net.asodev.islandutils.modules.ClassicAnnouncer; -import net.asodev.islandutils.modules.FriendsInGame; -import net.asodev.islandutils.modules.cosmetics.CosmeticSlot; -import net.asodev.islandutils.modules.cosmetics.CosmeticState; -import net.asodev.islandutils.modules.cosmetics.CosmeticType; -import net.asodev.islandutils.modules.music.MusicManager; -import net.asodev.islandutils.modules.splits.LevelTimer; -import net.asodev.islandutils.modules.splits.SplitManager; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.IslandSoundEvents; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientCommonPacketListenerImpl; -import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.client.multiplayer.CommonListenerCookie; -import net.minecraft.client.resources.sounds.SimpleSoundInstance; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.network.Connection; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.network.chat.TextColor; -import net.minecraft.network.protocol.game.ClientboundBossEventPacket; -import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; -import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket; -import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; -import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; -import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; -import net.minecraft.network.protocol.game.ClientboundSoundPacket; -import net.minecraft.network.protocol.game.ClientboundStopSoundPacket; -import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static net.asodev.islandutils.modules.FriendsInGame.TRANSACTION_ID; - -@Mixin(ClientPacketListener.class) -public abstract class PacketListenerMixin extends ClientCommonPacketListenerImpl { - - // I should really separate these mixins... - - @Shadow private CommandDispatcher commands; - - protected PacketListenerMixin(Minecraft minecraft, Connection connection, CommonListenerCookie commonListenerCookie) { - super(minecraft, connection, commonListenerCookie); - } - - @Inject(method = "handleSoundEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/util/thread/BlockableEventLoop;)V", shift = At.Shift.AFTER), cancellable = true) - public void handleCustomSoundEvent(ClientboundSoundPacket clientboundCustomSoundPacket, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - - ResourceLocation soundLoc = clientboundCustomSoundPacket.getSound().value().location(); - if (!soundLoc.getNamespace().equals("mcc")) return; - - if (soundLoc.getPath().startsWith("music.")) { - ChatUtils.debug("Starting music " + soundLoc); - MusicManager.onMusicSoundPacket(clientboundCustomSoundPacket, this.minecraft); - ci.cancel(); - } - if (MccIslandState.getGame() == Game.PARKOUR_WARRIOR_DOJO) { - LevelTimer.onSound(clientboundCustomSoundPacket); - } - } - - @Inject(method = "handleStopSoundEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/util/thread/BlockableEventLoop;)V", shift = At.Shift.AFTER), cancellable = true) - public void handleStopSoundEvent(ClientboundStopSoundPacket clientboundStopSoundPacket, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - ResourceLocation soundLoc = clientboundStopSoundPacket.getName(); - if (soundLoc == null || !soundLoc.getNamespace().equals("mcc")) return; - - if (soundLoc.getPath().startsWith("music.")) { - ChatUtils.debug("Stopping music " + soundLoc); - MusicManager.onMusicStopPacket(clientboundStopSoundPacket, this.minecraft); - ci.cancel(); - } - } - - @Inject(method = "handleContainerContent", at = @At("TAIL")) // Cosmetic previews, whenever we get our cosmetics back after closing menu - private void containerContent(ClientboundContainerSetContentPacket clientboundContainerSetContentPacket, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - Player player = Minecraft.getInstance().player; - if (player == null) return; // If no player, stop - if (clientboundContainerSetContentPacket.containerId() != 0) return; // If this is a chest, stop - - Inventory inventory = player.getInventory(); - CosmeticState.hatSlot.setOriginal(new CosmeticSlot(CosmeticType.HAT.getItem(inventory))); - CosmeticState.accessorySlot.setOriginal(new CosmeticSlot(CosmeticType.ACCESSORY.getItem(inventory))); - } - - private static Pattern timerPattern = Pattern.compile("(\\d+:\\d+)"); - @Inject(method = "handleBossUpdate", at = @At("HEAD")) // Discord presence, time left - private void handleBossUpdate(ClientboundBossEventPacket clientboundBossEventPacket, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - // Create a handler for the bossbar - ClientboundBossEventPacket.Handler bossbarHandler = new ClientboundBossEventPacket.Handler(){ - @Override - public void updateName(UUID uUID, Component component) { - if (!component.getString().contains(":")) return; // If we don't have a timer, move on with our lives - try { - String text = component.getString(); - Matcher matcher = timerPattern.matcher(text); - if (!matcher.find()) return; - String timer = matcher.group(1); - String[] split = timer.split(":"); // Split by the timer - String minsText = split[0]; // Get the left side - String secsText = split[1]; // Get the right side - - int mins = Integer.parseInt( minsText.substring( Math.max(minsText.length() - 2, 0)) ); // Get the last 2 character of the left side - int secs = Integer.parseInt( secsText.substring(0, 2) ); // Get the first 2 on the right side - - long secondsLeft = ((mins * 60L) + secs+1); - long finalUnix = System.currentTimeMillis() + (secondsLeft * 1000); // Get the timestamp when the game will end - - DiscordPresenceUpdator.timeLeftBossbar = uUID; // why do i do this again? - DiscordPresenceUpdator.updateTimeLeft(finalUnix); // Update our time left!! - } catch (Exception ignored) {} - } - @Override - public void remove(UUID uUID) { // tbf, i don't this is ever called, but y'know, just to be sure - if (DiscordPresenceUpdator.timeLeftBossbar == uUID) - DiscordPresenceUpdator.updateTimeLeft(null); - } - }; - clientboundBossEventPacket.dispatch(bossbarHandler); // Execute the handler! - } - - @Inject(method = "handleCommandSuggestions", cancellable = true, at = @At("HEAD")) // "Friends in this game: " - private void commandSuggestionsResponse(ClientboundCommandSuggestionsPacket clientboundCommandSuggestionsPacket, CallbackInfo ci) { - if (clientboundCommandSuggestionsPacket.id() == TRANSACTION_ID) { // If we get back suggestions from our previous request - ci.cancel(); // Stop minecraft... please. - List friends = clientboundCommandSuggestionsPacket // our friends suggestions - .suggestions() // the suggestions - .stream().map(ClientboundCommandSuggestionsPacket.Entry::text) // the text of the suggestions - .collect(Collectors.toList()); // a list of the suggestions - FriendsInGame.setFriends(friends); // Set our friends! - } - } - - TextColor textColor = ChatUtils.parseColor("#FFA800"); // Trap Title Text Color - Style style = Style.EMPTY.withColor(textColor); // Style for the trap color - @Inject(method = "setSubtitleText", at = @At("HEAD"), cancellable = true) - private void titleText(ClientboundSetSubtitleTextPacket clientboundSetSubtitleTextPacket, CallbackInfo ci) { - if (MccIslandState.getGame() == Game.HITW) { - ClassicAnnouncer.handleTrap(clientboundSetSubtitleTextPacket, ci); - } else if (MccIslandState.getGame() == Game.PARKOUR_WARRIOR_DOJO) { - LevelTimer instance = LevelTimer.getInstance(); - if (instance == null) return; - instance.handleSubtitle(clientboundSetSubtitleTextPacket, ci); - } - } - - @Inject(method = "setTitleText", at = @At("HEAD")) // Game Over Sound Effect - private void gameOver(ClientboundSetTitleTextPacket clientboundSetTitleTextPacket, CallbackInfo ci) { - if (MccIslandState.getGame() != Game.HITW) return; // Make sure we're playing HITW - if (!IslandOptions.getClassicHITW().isClassicHITW()) return; // Requires isClassicHITW - String title = clientboundSetTitleTextPacket.text().getString().toUpperCase(); // Get the title in upper case - - if (title.contains("GAME OVER")) { // If we got game over title, play sound - Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(IslandSoundEvents.ANNOUNCER_GAME_OVER, 1f)); // Play the sound!! - } - } - - @Inject(method = "handleSystemChat", at = @At("HEAD"), cancellable = true) - private void onChat(ClientboundSystemChatPacket clientboundSystemChatPacket, CallbackInfo ci) { - if (clientboundSystemChatPacket.overlay() || !MccIslandState.isOnline()) return; - - IslandUtilsEvents.Modifier modifier = new IslandUtilsEvents.Modifier<>(); - IslandUtilsEvents.CHAT_MESSAGE.invoker().onChatMessage(clientboundSystemChatPacket, modifier); - - modifier.withCancel(value -> ci.cancel()); - modifier.withReplacement(replacement -> { - ci.cancel(); - this.minecraft.getChatListener().handleSystemMessage(replacement, false); - }); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/notif/ServerSelectionMixin.java b/src/main/java/net/asodev/islandutils/mixins/notif/ServerSelectionMixin.java deleted file mode 100644 index 357614c5..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/notif/ServerSelectionMixin.java +++ /dev/null @@ -1,59 +0,0 @@ -package net.asodev.islandutils.mixins.notif; - -import net.asodev.islandutils.state.MccIslandNotifs; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; -import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList; -import net.minecraft.client.multiplayer.ServerData; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@Mixin(ServerSelectionList.OnlineServerEntry.class) -public class ServerSelectionMixin { - @Shadow @Final private JoinMultiplayerScreen screen; - @Shadow @Final private ServerData serverData; - private static ResourceLocation NOTIF_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/sprites/icon/unseen_notification.png"); - private static Component NOTIF_TITLE = Component.literal("Notifications").withStyle(Style.EMPTY.withUnderlined(true)); - - @Inject( - method = "render", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/multiplayer/ServerSelectionList$OnlineServerEntry;drawIcon(Lnet/minecraft/client/gui/GuiGraphics;IILnet/minecraft/resources/ResourceLocation;)V", - shift = At.Shift.AFTER - ) - ) - private void render(GuiGraphics guiGraphics, int index, int y, int x, int ew, int eh, int mouseX, int mouseY, boolean hovered, float d, CallbackInfo ci) { - if (!this.serverData.ip.toLowerCase().contains("mccisland.net")) return; - List notifs = MccIslandNotifs.getNotifLines(); - if (notifs.isEmpty()) return; - - List tooltip = new ArrayList<>(); - tooltip.add(NOTIF_TITLE); - tooltip.add(Component.empty()); - tooltip.addAll(notifs); - - int nx = x - 10 - 2; - int ny = y + 8 + 2; - - if (mouseX >= nx && mouseY >= ny && mouseX < nx + 10 && mouseY < ny + 10) { - guiGraphics.renderTooltip(Minecraft.getInstance().font, tooltip, Optional.empty(), mouseX, mouseY); - } - - guiGraphics.blit(RenderType::guiTextured, NOTIF_TEXTURE, nx, ny, 0, 0, 10, 10, 10, 10); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/plobby/PlobbyChestMixin.java b/src/main/java/net/asodev/islandutils/mixins/plobby/PlobbyChestMixin.java deleted file mode 100644 index 230ebad9..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/plobby/PlobbyChestMixin.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.asodev.islandutils.mixins.plobby; - -import net.asodev.islandutils.modules.plobby.PlobbyJoinCodeCopy; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.Utils; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.inventory.ClickType; -import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.ItemStack; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(AbstractContainerScreen.class) -public class PlobbyChestMixin extends Screen { - protected PlobbyChestMixin(Component component) { - super(component); - } - - @Inject(method = "slotClicked", at = @At("HEAD")) - private void slotClicked(Slot slot, int index, int j, ClickType clickType, CallbackInfo ci) { - if (!MccIslandState.isOnline()) return; - if (slot == null) return; - if (index != 8 && slot.hasItem()) return; - - ItemStack item = slot.getItem(); - ResourceLocation customItemID = Utils.getCustomItemID(item); - if (customItemID != null && !customItemID.getPath().equals(Utils.BLANK_ITEM_ID)) return; - - String code = PlobbyJoinCodeCopy.getJoinCodeFromItem(item); - if (code == null) return; - - PlobbyJoinCodeCopy.lastCopy = System.currentTimeMillis(); // We copied rn - this.minecraft.keyboardHandler.setClipboard(code); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/resources/MultiplayerJoinMixin.java b/src/main/java/net/asodev/islandutils/mixins/resources/MultiplayerJoinMixin.java deleted file mode 100644 index e225d9a3..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/resources/MultiplayerJoinMixin.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.asodev.islandutils.mixins.resources; - -import net.asodev.islandutils.IslandUtils; -import net.asodev.islandutils.util.resourcepack.ResourcePackUpdater; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.screens.ConfirmScreen; -import net.minecraft.client.gui.screens.ConnectScreen; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; -import net.minecraft.client.multiplayer.ServerData; -import net.minecraft.client.multiplayer.resolver.ServerAddress; -import net.minecraft.network.chat.Component; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(JoinMultiplayerScreen.class) -public class MultiplayerJoinMixin extends Screen { - - protected MultiplayerJoinMixin(Component component) { super(component); } - - @Inject(method = "join", at = @At("HEAD"), cancellable = true) - private void join(ServerData serverData, CallbackInfo ci) { - if (!IslandUtils.packUpdater.accepted && (IslandUtils.packUpdater.getting || ResourcePackUpdater.pack == null)) { - ci.cancel(); - - Component message = Component.literal("IslandUtils Music has not been downloaded").withStyle(ChatFormatting.AQUA); - Component title = Component.literal("Proceed?").withStyle(ChatFormatting.RED); - Component no = Component.literal("Cancel"); - Component yes = Component.literal("Continue joining"); - - ConfirmScreen screen = new ConfirmScreen((boo) -> confirm(boo, serverData), message, title, yes, no); - this.minecraft.setScreen(screen); - } - } - - void confirm(boolean bool, ServerData serverData) { - if (bool) { - IslandUtils.packUpdater.accepted = true; - ConnectScreen.startConnecting(this, this.minecraft, ServerAddress.parseString(serverData.ip), serverData, false, null); - } - else this.minecraft.setScreen(this); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/resources/PackRepositoryMixin.java b/src/main/java/net/asodev/islandutils/mixins/resources/PackRepositoryMixin.java deleted file mode 100644 index b80ed6c3..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/resources/PackRepositoryMixin.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.asodev.islandutils.mixins.resources; - -import net.asodev.islandutils.util.resourcepack.IslandUtilsRepositorySource; -import net.minecraft.server.packs.repository.PackRepository; -import net.minecraft.server.packs.repository.RepositorySource; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.HashSet; -import java.util.Set; - -@Mixin(PackRepository.class) -public class PackRepositoryMixin { - - @Shadow @Final @Mutable private Set sources; - - @Inject(method = "", at = @At("RETURN")) - private void init(RepositorySource[] repositorySources, CallbackInfo ci) { - sources = new HashSet<>(sources); - sources.add(new IslandUtilsRepositorySource()); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/splits/HologramMixin.java b/src/main/java/net/asodev/islandutils/mixins/splits/HologramMixin.java deleted file mode 100644 index c674929b..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/splits/HologramMixin.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.asodev.islandutils.mixins.splits; - -import net.asodev.islandutils.modules.splits.SplitManager; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.TimeUtil; -import net.minecraft.ChatFormatting; -import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TextColor; -import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; -import net.minecraft.world.entity.AreaEffectCloud; -import net.minecraft.world.entity.Entity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -@Mixin(value = ClientPacketListener.class, priority = 990) -public class HologramMixin { - TextColor redColor = TextColor.fromLegacyFormat(ChatFormatting.RED); - TextColor yellowColor = TextColor.fromLegacyFormat(ChatFormatting.YELLOW); - - @Inject(method = "handleSetEntityData", at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void handleEntityData(ClientboundSetEntityDataPacket clientboundSetEntityDataPacket, CallbackInfo ci, Entity entity) { - if (!(entity instanceof AreaEffectCloud hologram)) return; - if (!MccIslandState.isOnline() || MccIslandState.getGame() != Game.PARKOUR_WARRIOR_DOJO) return; - - Component customName = hologram.getCustomName(); - if (customName == null) return; - TextColor color = customName.getStyle().getColor(); - if (color == null) return; - if (!color.equals(redColor) && !color.equals(yellowColor)) return; - - String name = customName.getString(); - long seconds = TimeUtil.getTimeSeconds(name); - - ChatUtils.debug("Found course expiry: " + name + " (" + seconds + ")"); - SplitManager.setCurrentCourseExpiry(System.currentTimeMillis() + (seconds * 1000L)); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/ui/ChatScreenMixin.java b/src/main/java/net/asodev/islandutils/mixins/ui/ChatScreenMixin.java deleted file mode 100644 index a801ac8d..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/ui/ChatScreenMixin.java +++ /dev/null @@ -1,89 +0,0 @@ -package net.asodev.islandutils.mixins.ui; - -import net.asodev.islandutils.modules.ChatChannelButton; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.util.PlainTextButtonNoShadow; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.CommandSuggestions; -import net.minecraft.client.gui.screens.ChatScreen; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.ArrayList; -import java.util.List; - -import static net.asodev.islandutils.state.MccIslandState.isOnline; - -@Mixin(ChatScreen.class) -public abstract class ChatScreenMixin extends Screen { - @Shadow - CommandSuggestions commandSuggestions; - - protected ChatScreenMixin(Component component) { - super(component); - } - - private final List buttons = new ArrayList<>(); - private long lastPress = 0; - - @Unique - private static final int BUTTON_WIDTH = 43; - @Unique - private static final int BUTTON_HEIGHT = 9; - - @Inject(method = "init", at = @At("TAIL")) - private void init(CallbackInfo ci) { - if (!isOnline() || !IslandOptions.getMisc().showChannelSwitchers()) return; - buttons.clear(); - - int x = 2; - final var y = this.height - 14 - BUTTON_HEIGHT - 2; - - for (final var button : ChatChannelButton.currentButtons()) { - final var buttonWidget = new PlainTextButtonNoShadow( - x, - y, - BUTTON_WIDTH, - BUTTON_HEIGHT, - button.text(), - (d) -> Minecraft.getInstance().getConnection().sendCommand("chat " + button.name()), - Minecraft.getInstance().font - ); - buttons.add(buttonWidget); - addWidget(buttonWidget); - - x += BUTTON_WIDTH + 3; - } - } - - @Inject(method = "render", at = @At("TAIL")) - private void render(GuiGraphics guiGraphics, int i, int j, float f, CallbackInfo ci) { - CommandSuggestionsAccessor suggestionsAccessor = ((CommandSuggestionsAccessor) commandSuggestions); - if (suggestionsAccessor.suggestions() == null && suggestionsAccessor.commandUsage().size() == 0) { - buttons.forEach(btn -> btn.render(guiGraphics, i, j, f)); - } - } - - @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) - private void mouseClicked(double d, double e, int i, CallbackInfoReturnable cir) { - long timeNow = System.currentTimeMillis(); - if ((timeNow - lastPress) < 1000) return; - for (PlainTextButtonNoShadow button : buttons) { - if (button.isHoveredOrFocused()) { - button.onPress(); - lastPress = System.currentTimeMillis(); - cir.setReturnValue(true); - return; - } - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/ui/CommandSuggestionsAccessor.java b/src/main/java/net/asodev/islandutils/mixins/ui/CommandSuggestionsAccessor.java deleted file mode 100644 index 2779ab94..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/ui/CommandSuggestionsAccessor.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.asodev.islandutils.mixins.ui; - -import net.minecraft.client.gui.components.CommandSuggestions; -import net.minecraft.util.FormattedCharSequence; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -import java.util.List; - -@Mixin(CommandSuggestions.class) -public interface CommandSuggestionsAccessor { - - @Accessor("suggestions") - CommandSuggestions.SuggestionsList suggestions(); - - @Accessor("commandUsage") - List commandUsage(); - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/ui/FishingUpgradeHighlightMixin.java b/src/main/java/net/asodev/islandutils/mixins/ui/FishingUpgradeHighlightMixin.java deleted file mode 100644 index 82dac229..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/ui/FishingUpgradeHighlightMixin.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.asodev.islandutils.mixins.ui; - -import net.asodev.islandutils.modules.FishingUpgradeIcon; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.Utils; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.inventory.Slot; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.List; - -@Mixin(AbstractContainerScreen.class) -public class FishingUpgradeHighlightMixin { - @Inject(method = "renderSlot", at = @At(value = "TAIL")) - private void renderSlot(GuiGraphics guiGraphics, Slot slot, CallbackInfo ci) { - if (!slot.hasItem() || !IslandOptions.getMisc().isShowFishingUpgradeIcon()) return; - if (MccIslandState.getGame() != Game.FISHING) return; - FishingUpgradeIcon.render(slot, guiGraphics); - } -} diff --git a/src/main/java/net/asodev/islandutils/mixins/ui/PauseScreenMixin.java b/src/main/java/net/asodev/islandutils/mixins/ui/PauseScreenMixin.java deleted file mode 100644 index 508a3f53..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/ui/PauseScreenMixin.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.asodev.islandutils.mixins.ui; - -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.screens.ConfirmScreen; -import net.minecraft.client.gui.screens.PauseScreen; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(PauseScreen.class) -public class PauseScreenMixin extends Screen { - - @Mutable @Shadow private void onDisconnect() {} - - protected PauseScreenMixin(Component component) { - super(component); - } - - @Redirect( - method = "createPauseMenu", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/gui/components/Button;builder(Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/gui/components/Button$OnPress;)Lnet/minecraft/client/gui/components/Button$Builder;" - ) - ) - private Button.Builder createPause(Component component, Button.OnPress onPress) { - if (!MccIslandState.isOnline() || !IslandOptions.getMisc().isPauseConfirm()) return Button.builder(component, onPress); - if (component == CommonComponents.GUI_DISCONNECT) { - Component message = Component.literal("Are you sure you want to leave?").withStyle(ChatFormatting.AQUA); - Component no = Component.literal("Cancel"); - Component yes = Component.literal("Disconnect").withStyle(ChatFormatting.RED); - - return Button.builder(component, (button) -> { - ConfirmScreen screen = new ConfirmScreen((bool) -> callback(bool, button), Component.literal("Leave the server"), message, yes, no); - Minecraft.getInstance().setScreen(screen); - }); - } - return Button.builder(component, onPress); - } - - void callback(boolean confirm, Button buttonx) { - if (confirm) disconnect(buttonx); - else { Minecraft.getInstance().setScreen(new PauseScreen(true)); } - } - - void disconnect(Button button) { - button.active = false; - this.minecraft.getReportingContext().draftReportHandled(this.minecraft, this, this::onDisconnect, true); - } - -} diff --git a/src/main/java/net/asodev/islandutils/mixins/ui/SlotMixin.java b/src/main/java/net/asodev/islandutils/mixins/ui/SlotMixin.java deleted file mode 100644 index 37ad2de7..00000000 --- a/src/main/java/net/asodev/islandutils/mixins/ui/SlotMixin.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.asodev.islandutils.mixins.ui; - -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.Utils; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.ItemStack; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(Slot.class) -public abstract class SlotMixin { - - @Shadow - public abstract boolean hasItem(); - - @Shadow - public abstract ItemStack getItem(); - - @Inject(method = "isHighlightable", at = @At("HEAD"), cancellable = true) - private void isHighlightable(CallbackInfoReturnable cir) { - if (MccIslandState.isOnline() && MccIslandState.getGame().equals(Game.HUB)) { - ResourceLocation itemID = Utils.getCustomItemID(this.getItem()); - if (itemID == null) { - cir.setReturnValue(this.hasItem()); - } else if (itemID.getPath().equals("island_interface.generic.blank")) { - cir.setReturnValue(false); - } - } - } -} \ No newline at end of file diff --git a/src/main/java/net/asodev/islandutils/modules/ChatChannelButton.java b/src/main/java/net/asodev/islandutils/modules/ChatChannelButton.java deleted file mode 100644 index 79b41836..00000000 --- a/src/main/java/net/asodev/islandutils/modules/ChatChannelButton.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.asodev.islandutils.modules; - -import net.asodev.islandutils.modules.plobby.PlobbyFeatures; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.network.chat.Component; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static net.asodev.islandutils.util.ChatUtils.iconsFontStyle; - -/** - * A button that appears in the chat screen to select a chat channel. - * - * @param name the channel's name, as passed to the command {@code /channel } - * @param text the button's text contents, rendered without a background or shadow - */ -public record ChatChannelButton( - String name, - Component text -) { - private static final ChatChannelButton LOCAL = new ChatChannelButton( - "local", - Component.literal("\ue002").withStyle(iconsFontStyle) - ); - - private static final ChatChannelButton PARTY = new ChatChannelButton( - "party", - Component.literal("\ue003").withStyle(iconsFontStyle) - ); - - private static final ChatChannelButton TEAM = new ChatChannelButton( - "team", - Component.literal("\ue004").withStyle(iconsFontStyle) - ); - - private static final ChatChannelButton PLOBBY = new ChatChannelButton( - "plobby", - Component.literal("\ue011").withStyle(iconsFontStyle) - ); - - /** - * Gets an immutable list of the buttons to add to the chat screen at this point in time. - */ - public static List currentButtons() { - final var channels = new ArrayList(); - - channels.add(LOCAL); - channels.add(PARTY); - - if (MccIslandState.getGame().hasTeamChat()) { - channels.add(TEAM); - } - - if (PlobbyFeatures.isInPlobby()) { - channels.add(PLOBBY); - } - - return Collections.unmodifiableList(channels); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/ClassicAnnouncer.java b/src/main/java/net/asodev/islandutils/modules/ClassicAnnouncer.java deleted file mode 100644 index df481711..00000000 --- a/src/main/java/net/asodev/islandutils/modules/ClassicAnnouncer.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.asodev.islandutils.modules; - -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.util.ChatUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.sounds.SimpleSoundInstance; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.network.chat.TextColor; -import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.SoundEvent; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import static net.minecraft.network.chat.Component.literal; - -public class ClassicAnnouncer { - - public static long lastTrapTimestamp = 0; - public static String trap; - - static TextColor textColor = ChatUtils.parseColor("#FFA800"); // Trap Title Text Color - static Style style = Style.EMPTY.withColor(textColor); // Style for the trap color - public static void handleTrap(ClientboundSetSubtitleTextPacket clientboundSetSubtitleTextPacket, CallbackInfo ci) { - if (!IslandOptions.getClassicHITW().isClassicHITW()) return; // Requires isClassicHITW - String trap = clientboundSetSubtitleTextPacket.text().getString(); // Get the string version of the subtitle - - boolean isTrap = false; // Get all the elements in this component - for (Component component : clientboundSetSubtitleTextPacket.text().toFlatList()) { - if (component.getStyle().isObfuscated()) { return; } // If this component is obfuscated, it's the animation before the trap - if (component.getStyle().getColor() != null && component.getStyle().getColor().equals(textColor)) - isTrap = true; // If it's the gold color of the trap subtitle, it's a trap! - } - if (!isTrap) return; // If we didn't find the trap, we can just stop - - String change = changeName(trap); // Check for the changed trap names - if (change != null) { // If we have changed the name of the trap - Minecraft.getInstance().gui.setSubtitle(literal(change).withStyle(style)); // Send our own subtitle - ci.cancel(); // Cancel minecraft executing futher - } - - long timestamp = System.currentTimeMillis(); // This just ensures we don't play the sound twice - if ((timestamp - ClassicAnnouncer.lastTrapTimestamp) < 50) return; // 50ms delay - ClassicAnnouncer.lastTrapTimestamp = timestamp; - - trap = trap.replaceAll("([ \\-!])","").toLowerCase(); // Convert the trap to a lowercase space-less string - - try { - ClassicAnnouncer.trap = trap; // Set the trap to the one we just found - ResourceLocation sound = ResourceLocation.fromNamespaceAndPath("island", "announcer." + trap); // island:announcer.(trap) -> The sound location - SoundInstance soundInstance = SimpleSoundInstance.forUI(SoundEvent.createVariableRangeEvent(sound), 1f, 1f); - Minecraft.getInstance().getSoundManager().play(soundInstance); // Play the sound!! - } catch (Exception e) { - e.printStackTrace(); // Something went horribly wrong, probably an invalid character - } - } - - private static String changeName(String originalTrap) { - return switch (originalTrap) { - case "Feeling Hot" -> "What in the Blazes"; - case "Hot Coals" -> "Feeling Hot"; - case "Blast-Off" -> "Kaboom"; - case "Pillagers" -> "So Lonely"; - case "Leg Day" -> "Molasses"; - case "Snowball Fight" -> "Jack Frost"; - default -> null; - }; - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/DisguiseKeybind.java b/src/main/java/net/asodev/islandutils/modules/DisguiseKeybind.java deleted file mode 100644 index a497a8f7..00000000 --- a/src/main/java/net/asodev/islandutils/modules/DisguiseKeybind.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.asodev.islandutils.modules; - -import net.asodev.islandutils.IslandUtilsClient; -import net.asodev.islandutils.state.MccIslandState; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; - -public class DisguiseKeybind { - private static final Long disguiseCooldownTime = 1000L; - private static long lastDisguiseUseTimestamp = -1; - - public static void registerDisguiseInput() { - ClientTickEvents.END_CLIENT_TICK.register(client -> { - if (IslandUtilsClient.disguiseKeyBind.isDown() && client.player != null) { - if (attemptUseDisguiseKey() && MccIslandState.isOnline()) { - client.player.connection.sendCommand("disguise"); - } - } - }); - } - - private static Boolean attemptUseDisguiseKey() { - long currentTimestamp = System.currentTimeMillis(); - if (currentTimestamp - lastDisguiseUseTimestamp < disguiseCooldownTime) - return false; - lastDisguiseUseTimestamp = currentTimestamp; - return true; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/FishingUpgradeIcon.java b/src/main/java/net/asodev/islandutils/modules/FishingUpgradeIcon.java deleted file mode 100644 index e9e9cdaf..00000000 --- a/src/main/java/net/asodev/islandutils/modules/FishingUpgradeIcon.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.asodev.islandutils.modules; - -import net.asodev.islandutils.util.Utils; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.inventory.Slot; - -import java.util.List; - -public class FishingUpgradeIcon { - private static final ResourceLocation UPGRADE_ICON_LOCATION = ResourceLocation.fromNamespaceAndPath("island", "upgrade"); - - public static void render(Slot slot, GuiGraphics guiGraphics) { - List lore = Utils.getLores(slot.getItem()); - if (lore == null) return; - boolean canUpgrade = lore.stream().anyMatch(c -> c.getString().contains("Left-Click to Upgrade")); - if (!canUpgrade) return; - - int x = slot.x + 1; - int y = slot.y + 1; - guiGraphics.pose().pushPose(); - guiGraphics.pose().translate(0.0F, 0.0F, 400F); - guiGraphics.blitSprite(RenderType::guiTextured, UPGRADE_ICON_LOCATION, x, y, 16, 16); - guiGraphics.pose().popPose(); - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/FriendsInGame.java b/src/main/java/net/asodev/islandutils/modules/FriendsInGame.java deleted file mode 100644 index cbf90e19..00000000 --- a/src/main/java/net/asodev/islandutils/modules/FriendsInGame.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.asodev.islandutils.modules; - -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.options.categories.MiscOptions; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.Scheduler; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.client.multiplayer.PlayerInfo; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ServerboundCommandSuggestionPacket; - -import java.util.ArrayList; -import java.util.List; - -import static net.asodev.islandutils.util.ChatUtils.iconsFontStyle; - -public class FriendsInGame { - public static final int TRANSACTION_ID = 6775161; - private static List friends = new ArrayList<>(); - - public static void init() { - IslandUtilsEvents.GAME_UPDATE.register((game) -> { - friends.clear(); - ServerboundCommandSuggestionPacket packet = new ServerboundCommandSuggestionPacket(TRANSACTION_ID, "/friend remove "); - ClientPacketListener connection = Minecraft.getInstance().getConnection(); - if (connection != null) connection.send(packet); - }); - } - - public static void setFriends(List friends) { - FriendsInGame.friends = friends; - Scheduler.schedule(35, FriendsInGame::sendFriendsInGame); - } - - public static void sendFriendsInGame(Minecraft client) { - if (!shouldSendFriends()) return; - StringBuilder friendsInThisGame = new StringBuilder(); - boolean hasFriends = false; - - ClientPacketListener connection = client.getConnection(); - if (connection == null) return; - for (PlayerInfo p : connection.getOnlinePlayers()) { - String name = p.getProfile().getName(); - if (friends.contains(name)) { - hasFriends = true; - friendsInThisGame.append(", ").append(name); - } - } - if (!hasFriends) return; - String friendString = friendsInThisGame.toString().replaceFirst(", ",""); - - String text = "Friends in this game"; - if (MccIslandState.getGame() == Game.HUB) text = "Friends in this lobby"; - - Component component = Component.literal("[").withStyle(ChatFormatting.GREEN) - .append(Component.literal("\ue001").withStyle(iconsFontStyle)) - .append(Component.literal("] " + text + ": ").withStyle(ChatFormatting.GREEN)) - .append(Component.literal(friendString).withStyle(ChatFormatting.YELLOW)); - ChatUtils.send(component); - } - - public static boolean shouldSendFriends() { - MiscOptions misc = IslandOptions.getMisc(); - return MccIslandState.getGame() == Game.HUB ? misc.isShowFriendsInLobby() : misc.isShowFriendsInGame(); - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/NoxesiumIntegration.java b/src/main/java/net/asodev/islandutils/modules/NoxesiumIntegration.java deleted file mode 100644 index 51a7489d..00000000 --- a/src/main/java/net/asodev/islandutils/modules/NoxesiumIntegration.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.asodev.islandutils.modules; - -import com.noxcrew.noxesium.NoxesiumFabricMod; -import com.noxcrew.noxesium.network.NoxesiumPackets; -import com.noxcrew.noxesium.network.clientbound.ClientboundMccServerPacket; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NoxesiumIntegration { - private static Logger LOGGER = LoggerFactory.getLogger(NoxesiumIntegration.class); - - public void init() { - NoxesiumFabricMod.initialize(); - - NoxesiumPackets.CLIENT_MCC_SERVER.addListener(this, (any, packet, ctx) -> { - handleServerPacket(packet); - }); - } - - private void handleServerPacket(ClientboundMccServerPacket packet) { - MccIslandState.setSubType(packet.subType()); - try { - MccIslandState.setGame(Game.fromPacket(packet)); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/cosmetics/Cosmetic.java b/src/main/java/net/asodev/islandutils/modules/cosmetics/Cosmetic.java deleted file mode 100644 index 99e6e3dc..00000000 --- a/src/main/java/net/asodev/islandutils/modules/cosmetics/Cosmetic.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.asodev.islandutils.modules.cosmetics; - -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.Nullable; - -public class Cosmetic { - - @Nullable public CosmeticSlot hover; - @Nullable private CosmeticSlot original; - @Nullable public CosmeticSlot preview; - - public CosmeticType type; - - public void setOriginal(CosmeticSlot original) { - this.original = original; - } - - public Cosmetic(CosmeticType type) { - this.type = type; - } - - public CosmeticSlot getContents() { - if (preview != null) return preview; - if (hover != null) return hover; - if (original != null) return original; - return new CosmeticSlot(ItemStack.EMPTY, null); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticSlot.java b/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticSlot.java deleted file mode 100644 index 3d8b4617..00000000 --- a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticSlot.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.asodev.islandutils.modules.cosmetics; - -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import org.jetbrains.annotations.Nullable; - -import static net.asodev.islandutils.modules.cosmetics.CosmeticState.itemsMatch; - -public class CosmeticSlot { - - public ItemStack item; - @Nullable public Slot slot; - - public CosmeticSlot(Slot slot) { - this(slot.getItem(), slot); - } - - public CosmeticSlot(ItemStack item) { - this(item, null); - } - - public CosmeticSlot(ItemStack item, @Nullable Slot slot) { - this.item = item; - this.slot = slot; - } - - public ItemStack getItem(@Nullable ChestMenu menu) { - if (menu == null || slot == null) return item; - Slot menuSlot = menu.getSlot(slot.index); - if (menuSlot == null || !menuSlot.hasItem() || menuSlot.getItem().is(Items.AIR)) return item; - ItemStack menuItem = menuSlot.getItem(); - - if (!itemsMatch(menuItem, item)) return item; - else return menuItem; - } - - public boolean matchesSlot(@Nullable Slot checkSlot) { - if (checkSlot == null) return false; - if (this.slot == null) return false; - if (this.slot.index != checkSlot.index) return false; - return CosmeticState.itemsMatch(checkSlot.getItem(), item); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticState.java b/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticState.java deleted file mode 100644 index d9840ea6..00000000 --- a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticState.java +++ /dev/null @@ -1,109 +0,0 @@ -package net.asodev.islandutils.modules.cosmetics; - -import net.asodev.islandutils.util.Utils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.core.component.DataComponents; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.component.DyedItemColor; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class CosmeticState { - - public static final ResourceLocation MCC_ICONS = ResourceLocation.fromNamespaceAndPath("mcc", "icon"); - public static Component HAIR_COMP = Component.literal("\uE0E7").setStyle(Style.EMPTY.withFont(MCC_ICONS)); - public static Component HAT_COMP = Component.literal("\uE0E8").setStyle(Style.EMPTY.withFont(MCC_ICONS)); - public static Component ACCESSORY_COMP = Component.literal("\uE0DA").setStyle(Style.EMPTY.withFont(MCC_ICONS)); - - @Nullable public static Player inspectingPlayer; - public static float yRot = 155; - public static float xRot = -5; - - public static Cosmetic hatSlot = new Cosmetic(CosmeticType.HAT); - public static Cosmetic accessorySlot = new Cosmetic(CosmeticType.ACCESSORY); - public static Cosmetic mainHandSlot = new Cosmetic(CosmeticType.MAIN_HAND); - @Nullable public static Integer hoveredColor; - - public static Cosmetic getCosmeticByType(CosmeticType type) { - switch (type) { - case HAT -> { return hatSlot; } - case ACCESSORY -> { return accessorySlot; } - case MAIN_HAND -> { return mainHandSlot; } - } - return null; - } - - public static Player getInspectingPlayer() { - return (inspectingPlayer == null) ? Minecraft.getInstance().player : inspectingPlayer; - } - - public static boolean canBeEquipped(ItemStack stack) { - List lores = Utils.getLores(stack); - if (lores == null) return false; - return lores.stream().anyMatch(p -> p.getString().contains("Left-Click to Equip")); - } - - public static boolean isColoredItem(ItemStack item) { - if (!item.is(Items.LEATHER_HORSE_ARMOR)) return false; - ResourceLocation customItemID = Utils.getCustomItemID(item); - if (customItemID == null) return false; - return customItemID.getPath().equals("island_interface.misc.color"); - } - - public static Integer getColor(ItemStack itemStack) { - DyedItemColor dyedItemColor = itemStack.get(DataComponents.DYED_COLOR); - return dyedItemColor != null ? dyedItemColor.rgb() : null; - } - public static ItemStack applyColor(ItemStack itemStack) { - if (hoveredColor == null) return itemStack; - DyedItemColor itemColor = new DyedItemColor(hoveredColor); - itemStack.set(DataComponents.DYED_COLOR, itemColor); - return itemStack; - } - - public static CosmeticType getType(ItemStack item) { - ResourceLocation itemId = Utils.getCustomItemID(item); - if (itemId == null) return null; - String path = itemId.getPath(); - if (path.endsWith(".icon_empty") || path.endsWith(".icon")) return null; - if (path.contains("hat.") || path.contains("hair.")) return CosmeticType.HAT; - if (path.contains("accessory.")) return CosmeticType.ACCESSORY; - if (path.startsWith("island_lobby.fishing.rods")) return CosmeticType.MAIN_HAND; - return null; - } - - public static boolean isCosmeticMenu(AbstractContainerScreen screen) { - if (screen.getTitle().getString().contains("WARDROBE")) return true; - - ChestMenu menu = screen.getMenu(); - List slots = new ArrayList<>(menu.slots.stream().map(Slot::getItem).toList()); - slots.add(menu.getCarried()); - for (ItemStack slot : slots){ - CosmeticType type = CosmeticState.getType(slot); - if (type != null) return true; - } - return false; - } - - public static boolean itemsMatch(ItemStack item, ItemStack compare) { - ItemStack item1 = item != null ? item : ItemStack.EMPTY; - ItemStack item2 = compare != null ? compare : ItemStack.EMPTY; - - ResourceLocation item1ID = Utils.getCustomItemID(item1); - ResourceLocation item2ID = Utils.getCustomItemID(item2); - - return Objects.equals(item1ID, item2ID); - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticType.java b/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticType.java deleted file mode 100644 index 2a522ada..00000000 --- a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticType.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.asodev.islandutils.modules.cosmetics; - -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.item.ItemStack; - -public enum CosmeticType { - - HAT(EquipmentSlot.HEAD, EquipmentSlot.HEAD.getIndex(36)), - ACCESSORY(EquipmentSlot.OFFHAND, 40), - MAIN_HAND(EquipmentSlot.MAINHAND, -1); - - EquipmentSlot slot; - int index; - - CosmeticType(EquipmentSlot slot, int index) { - this.slot = slot; - this.index = index; - } - - public ItemStack getItem(Inventory playerInventory) { - if (this.index == -1) return playerInventory.getSelectedItem(); - return playerInventory.getItem(this.index); - } - - public EquipmentSlot getSlot() { - return slot; - } - - public int getIndex() { - return index; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticUI.java b/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticUI.java deleted file mode 100644 index 6013d2a7..00000000 --- a/src/main/java/net/asodev/islandutils/modules/cosmetics/CosmeticUI.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.asodev.islandutils.modules.cosmetics; - -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.InventoryScreen; -import net.minecraft.world.entity.LivingEntity; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -public class CosmeticUI { - - public static void renderPlayerInInventory(GuiGraphics guiGraphics, int x, int y, int size, LivingEntity livingEntity) { - float yRot = CosmeticState.yRot; - float xRot = (float)Math.atan(CosmeticState.xRot / 40.0f); - - // save rotations - float preYBodyRot = livingEntity.yBodyRot; - float preYRot = livingEntity.getYRot(); - float preXrot = livingEntity.getXRot(); - float preYHeadRot0 = livingEntity.yHeadRotO; - float preYHeadRot = livingEntity.yHeadRot; - - // set rotations - livingEntity.yBodyRot = yRot; - livingEntity.setYRot(yRot); - livingEntity.yHeadRot = livingEntity.yBodyRot; - livingEntity.yHeadRotO = livingEntity.yBodyRot; - livingEntity.setXRot(xRot * -20f); // magic value - InventoryScreen#renderEntityInInventoryFollowsMouse [setXRot] - - guiGraphics.pose().pushPose(); - guiGraphics.pose().translate(0f, 0f, 105f); // translate to 105 on the z - float f = 0.0625F; // magic value - InventoryScreen#renderBg [renderEntityInInventoryFollowsMouse] - float livingEntityScale = livingEntity.getScale(); - - // here we make some quaternions and do some math with them - // idfk this is just what the inventory code does :sob: - // magic values - InventoryScreen#renderEntityInInventoryFollowsMouse - Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F); - Quaternionf quaternionf2 = (new Quaternionf()).rotateX(xRot * 20.0F * 0.017453292F); - quaternionf.mul(quaternionf2); - Vector3f vector3f = new Vector3f(0.0F, livingEntity.getBbHeight() / 2.0F + f * livingEntityScale, 0.0F); - InventoryScreen.renderEntityInInventory( - guiGraphics, - x, y - size, - size / livingEntityScale, - vector3f, - quaternionf, - quaternionf2, - livingEntity - ); - guiGraphics.pose().popPose(); - - // restore rotations - livingEntity.yBodyRot = preYBodyRot; - livingEntity.setYRot(preYRot); - livingEntity.setXRot(preXrot); - livingEntity.yHeadRotO = preYHeadRot0; - livingEntity.yHeadRot = preYHeadRot; - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/crafting/CraftingMenuType.java b/src/main/java/net/asodev/islandutils/modules/crafting/CraftingMenuType.java deleted file mode 100644 index f5841bf9..00000000 --- a/src/main/java/net/asodev/islandutils/modules/crafting/CraftingMenuType.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.asodev.islandutils.modules.crafting; - -public enum CraftingMenuType { - FORGE, - ASSEMBLER -} diff --git a/src/main/java/net/asodev/islandutils/modules/crafting/CraftingToast.java b/src/main/java/net/asodev/islandutils/modules/crafting/CraftingToast.java deleted file mode 100644 index 0f81b5c5..00000000 --- a/src/main/java/net/asodev/islandutils/modules/crafting/CraftingToast.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.asodev.islandutils.modules.crafting; - -import net.asodev.islandutils.modules.crafting.state.CraftingItem; -import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.toasts.Toast; -import net.minecraft.client.gui.components.toasts.ToastManager; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; - -import static net.asodev.islandutils.util.Utils.MCC_HUD_FONT; - -public class CraftingToast implements Toast { - private static final ResourceLocation ISLAND_TOASTS_TEXTURE = ResourceLocation.fromNamespaceAndPath("island", "toasts"); - private static final long DISPLAY_TIME = 5000; // 5s - - ItemStack itemStack; - Component displayName; - final Component description = Component.literal("CRAFTING COMPLETE!") - .withStyle(MCC_HUD_FONT.withColor(ChatFormatting.WHITE)); - - private Visibility wantedVisibility = Visibility.HIDE; - - public CraftingToast(CraftingItem craftingItem) { - itemStack = craftingItem.getStack(); - displayName = craftingItem.getTitle(); - } - - @Override - public Visibility getWantedVisibility() { - return wantedVisibility; - } - - @Override - public void update(ToastManager toastManager, long l) { - this.wantedVisibility = (l >= DISPLAY_TIME * toastManager.getNotificationDisplayTimeMultiplier()) ? Visibility.HIDE : Visibility.SHOW; - } - - @Override - public void render(GuiGraphics guiGraphics, Font font, long l) { - guiGraphics.blitSprite(RenderType::guiTextured, ISLAND_TOASTS_TEXTURE, 0, 0, this.width(), this.height()); - int y = 7; - guiGraphics.drawString(font, description, 30, y, -16777216, false); - y += 5 + 4; - guiGraphics.drawString(font, displayName, 30, y, -11534256, false); - - guiGraphics.renderFakeItem(itemStack, 8, 8); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/crafting/CraftingUI.java b/src/main/java/net/asodev/islandutils/modules/crafting/CraftingUI.java deleted file mode 100644 index cf2ddcf5..00000000 --- a/src/main/java/net/asodev/islandutils/modules/crafting/CraftingUI.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.asodev.islandutils.modules.crafting; - -import net.asodev.islandutils.modules.crafting.state.CraftingItem; -import net.asodev.islandutils.modules.crafting.state.CraftingItems; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.TimeUtil; -import net.asodev.islandutils.util.Utils; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.network.chat.TextColor; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; - -import java.util.List; -import java.util.Objects; - -public class CraftingUI { - public static Style CHEST_BACKGROUND_STYLE = Style.EMPTY.withColor(ChatFormatting.WHITE).withFont(ResourceLocation.fromNamespaceAndPath("mcc", "chest_backgrounds")); - private static Component assemblerComponent; - private static Component forgeComponent; - - public static CraftingMenuType craftingMenuType(Component component) { - if (assemblerComponent == null || forgeComponent == null) return null; - - if (component.contains(assemblerComponent)) return CraftingMenuType.ASSEMBLER; - if (component.contains(forgeComponent)) return CraftingMenuType.FORGE; - return null; - } - - private static TextColor timeLeftColor = ChatUtils.parseColor("#FF5556"); - public static void analyseCraftingItem(CraftingMenuType type, ItemStack item, int slot) { - if (!isInputSlot(slot)) { - CraftingItems.removeSlot(type, slot); - return; - } - - List lores = Utils.getLores(item); - if (lores != null && isActive(lores)) { - String timeLeftString = null; - for (Component line : lores) { - Component firstComponent = line.getSiblings().stream().findFirst().orElse(null); - TextColor color = firstComponent == null ? null : firstComponent.getStyle().getColor(); - if (!Objects.equals(color, timeLeftColor)) continue; - timeLeftString = line.getString(); - break; - } - if (timeLeftString != null) { - long timeLeft = TimeUtil.getTimeSeconds(timeLeftString) + 60; - long finishTimestamp = System.currentTimeMillis() + (timeLeft * 1000); - - CraftingItem craftingItem = new CraftingItem(); - craftingItem.setCraftingMenuType(type); - craftingItem.setFinishesCrafting(finishTimestamp); - craftingItem.setSlot(slot); - craftingItem.setType(item.getItem()); - craftingItem.setTitle(item.getHoverName()); - craftingItem.setCustomModelData(Math.round(Utils.customModelData(item))); // Keeping this as int so existing crafting items don't break - - CraftingItems.addItem(craftingItem); - - ChatUtils.debug("[#" + slot + " " + type.name() + "] Found active craft: " + item.getDisplayName().getString() + " (" + timeLeft + ")"); - return; - } - } - - CraftingItems.removeSlot(type, slot); - ChatUtils.debug(type.name() + " - Found empty craft slot: " + slot + "!"); - } - - private static boolean isActive(List lores) { - return lores.stream().anyMatch(p -> p.getString().contains("Shift-Click to Cancel")); - } - private static boolean isInputSlot(int slot) { - return slot >= 19 && slot <= 23; - } - - public static void setAssemblerCharacter(String assemblerCharacter) { - CraftingUI.assemblerComponent = Component.literal(assemblerCharacter).withStyle(CHEST_BACKGROUND_STYLE); - } - public static void setForgeCharacter(String forgeCharacter) { - CraftingUI.forgeComponent = Component.literal(forgeCharacter).withStyle(CHEST_BACKGROUND_STYLE); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingItem.java b/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingItem.java deleted file mode 100644 index 44cb242e..00000000 --- a/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingItem.java +++ /dev/null @@ -1,151 +0,0 @@ -package net.asodev.islandutils.modules.crafting.state; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.asodev.islandutils.modules.crafting.CraftingMenuType; -import net.minecraft.core.Holder; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.component.DataComponents; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.component.CustomModelData; - -import java.util.List; - -import static net.asodev.islandutils.util.ChatUtils.iconsFontStyle; - -public class CraftingItem { - - private Component title; - private Item type; - private int customModelData; - - private long finishesCrafting; - private CraftingMenuType craftingMenuType; - private boolean hasSentNotif = false; - private int slot; - - public JsonObject toJson() { - JsonObject object = new JsonObject(); - object.addProperty("title", Component.Serializer.toJson(title, RegistryAccess.EMPTY)); - object.addProperty("type", BuiltInRegistries.ITEM.getKey(type).toString()); - object.addProperty("customModelData", customModelData); - object.addProperty("finishesCrafting", finishesCrafting); - object.addProperty("craftingMenuType", craftingMenuType.name()); - object.addProperty("hasSentNotif", hasSentNotif); - object.addProperty("slot", slot); - return object; - } - - public static CraftingItem fromJson(JsonElement element) { - JsonObject object = element.getAsJsonObject(); - CraftingItem item = new CraftingItem(); - - String jsonTitle = object.get("title").getAsString(); - item.setTitle(Component.Serializer.fromJson(jsonTitle, RegistryAccess.EMPTY)); - - ResourceLocation typeKey = ResourceLocation.parse(object.get("type").getAsString()); - Holder.Reference itemType = BuiltInRegistries.ITEM.get(typeKey) - .orElseThrow(() -> new IllegalStateException("Item with type " + typeKey + " does not exist.")); - item.setType(itemType.value()); - - item.setCustomModelData(object.get("customModelData").getAsInt()); - - - String craftingTypeString = object.get("craftingMenuType").getAsString(); - item.setCraftingMenuType(CraftingMenuType.valueOf(craftingTypeString.toUpperCase())); - - item.setFinishesCrafting(object.get("finishesCrafting").getAsLong()); - item.setHasSentNotif(object.get("hasSentNotif").getAsBoolean()); - item.setSlot(object.get("slot").getAsInt()); - - return item; - } - - public ItemStack getStack() { - ItemStack stack = new ItemStack(type); - stack.set(DataComponents.CUSTOM_MODEL_DATA, new CustomModelData(List.of((float) customModelData), List.of(), List.of(), List.of())); - return stack; - } - - public boolean isComplete() { - return System.currentTimeMillis() >= this.getFinishesCrafting(); - } - - public Component getTitle() { - return title; - } - - public Component getTypeIcon() { - String icon = this.getCraftingMenuType() == CraftingMenuType.FORGE ? "\ue006" : "\ue007"; - return Component.literal(icon).withStyle(iconsFontStyle); - } - - public void setTitle(Component title) { - this.title = title; - } - - public Item getType() { - return type; - } - - public void setType(Item type) { - this.type = type; - } - - public int getCustomModelData() { - return customModelData; - } - - public void setCustomModelData(int customModelData) { - this.customModelData = customModelData; - } - - public long getFinishesCrafting() { - return finishesCrafting; - } - - public void setFinishesCrafting(long finishesCrafting) { - this.finishesCrafting = finishesCrafting; - } - - public CraftingMenuType getCraftingMenuType() { - return craftingMenuType; - } - - public void setCraftingMenuType(CraftingMenuType craftingMenuType) { - this.craftingMenuType = craftingMenuType; - } - - public boolean hasSentNotif() { - return hasSentNotif; - } - - public void setHasSentNotif(boolean hasSentNotif) { - this.hasSentNotif = hasSentNotif; - } - - public int getSlot() { - return slot; - } - - public void setSlot(int slot) { - this.slot = slot; - } - - @Override - public String toString() { - return "CraftingItem{" + - "title=" + title + - ", type=" + type + - ", customModelData=" + customModelData + - ", finishesCrafting=" + finishesCrafting + - ", craftingMenuType=" + craftingMenuType + - ", hasSentNotif=" + hasSentNotif + - ", slot=" + slot + - '}'; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingItems.java b/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingItems.java deleted file mode 100644 index 74016e24..00000000 --- a/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingItems.java +++ /dev/null @@ -1,133 +0,0 @@ -package net.asodev.islandutils.modules.crafting.state; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import net.asodev.islandutils.modules.crafting.CraftingMenuType; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.Scheduler; -import net.asodev.islandutils.util.Utils; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.world.item.Items; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongepowered.asm.mixin.injection.At; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import static net.asodev.islandutils.util.resourcepack.ResourcePackOptions.islandFolder; - -public class CraftingItems { - private static Logger logger = LoggerFactory.getLogger(CraftingItems.class); - private static final File file = new File(islandFolder + "/crafting.json"); - private static final List items = new ArrayList<>(); - - private static boolean saveQueued = false; - - /** - * Adds an item into the items - * @param item The CraftingItem to add - */ - public static void addItem(CraftingItem item) { - removeSlot(item.getCraftingMenuType(), item.getSlot()); - items.add(item); - - save(); - } - - /** - * Remove items in the same menu & slot - * - * @param type The menu type (Forge or Assembler) - * @param slot The slot you wish to remove - */ - public static void removeSlot(CraftingMenuType type, int slot) { - boolean wasRemoved = items.removeIf(i -> i.getCraftingMenuType() == type && i.getSlot() == slot); - if (wasRemoved) save(); - } - - public static void submit(Runnable task) { - Utils.savingQueue.submit(task); - } - public static List getItems() { - return items; - } - - public static void load() throws Exception { - String string = Utils.readFile(file); - if (string == null) return; - - JsonObject object = new Gson().fromJson(string, JsonObject.class); - JsonArray array = object.get("items").getAsJsonArray(); - array.forEach(element -> { - try { - items.add(CraftingItem.fromJson(element)); - } catch (Exception e) { - logger.error("Failed to load crafting item", e); - } - }); - } - public static void save() { - if (saveQueued) { - return; - } - - saveQueued = true; - Scheduler.schedule(3, (client) -> { - Utils.savingQueue.submit(CraftingItems::saveSync); - saveQueued = false; - }); - } - public static void saveSync() { - try { - Utils.assertIslandFolder(); - - JsonArray array = new JsonArray(); - for (CraftingItem item : getItems()) { - try { - array.add(item.toJson()); - } catch (Exception e) { - logger.error("Failed to save item: {}", item, e); - } - } - - JsonObject object = new JsonObject(); - object.add("items", array); - object.addProperty("savedAt", System.currentTimeMillis()); - object.addProperty("version", 1); - - Utils.writeFile(file, object.toString()); - logger.info("Saved Crafting Items"); - } catch (Exception e) { - logger.error("Failed to save crafting items", e); - } - } - - // DEBUG - public static void addDebugItem(String color, Integer slot, Integer delay) { - CraftingItem item = new CraftingItem(); - item.setTitle( - Component.literal("Refined Quest Spirit") - .setStyle(Style.EMPTY.withColor(ChatUtils.parseColor(color))) - ); - // Common = #FFFFFF - // Uncommon = #1EFF00 - // Rare = #0070DD - // Epic = #A335EE - // Legendary = #FF8000 - // Mythic = #F94242 - - item.setCustomModelData(7924); - item.setFinishesCrafting(System.currentTimeMillis() + delay * 1000); - item.setHasSentNotif(false); - item.setSlot(slot); - item.setCraftingMenuType(CraftingMenuType.ASSEMBLER); - item.setType(Items.POPPED_CHORUS_FRUIT); - - CraftingItems.addItem(item); - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingNotifier.java b/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingNotifier.java deleted file mode 100644 index 7f7cd8ca..00000000 --- a/src/main/java/net/asodev/islandutils/modules/crafting/state/CraftingNotifier.java +++ /dev/null @@ -1,147 +0,0 @@ -package net.asodev.islandutils.modules.crafting.state; - -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import net.asodev.islandutils.modules.crafting.CraftingToast; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.options.categories.CraftingOptions; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.IslandSoundEvents; -import net.asodev.islandutils.util.Scheduler; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.LoadingOverlay; -import net.minecraft.client.resources.sounds.SimpleSoundInstance; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; -import org.apache.commons.lang3.time.DurationFormatUtils; - -import java.util.List; - -import static net.asodev.islandutils.util.IslandUtilsCommand.cantUseDebugError; -import static net.asodev.islandutils.util.Utils.MCC_HUD_FONT; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; - -public class CraftingNotifier implements ClientTickEvents.EndTick { - private int tick = 0; - public CraftingNotifier() { - ClientTickEvents.END_CLIENT_TICK.register(this); - } - - public void update(Minecraft client) { - boolean anythingHasChanged = false; - for (CraftingItem item : CraftingItems.getItems()) { - if (!item.isComplete()) continue; - if (!MccIslandState.isOnline() || client.getOverlay() instanceof LoadingOverlay) continue; - if (item.hasSentNotif()) continue; - - sendNotif(client, item); - anythingHasChanged = true; - } - if (anythingHasChanged) CraftingItems.save(); - } - - public void sendNotif(Minecraft client, CraftingItem item) { - CraftingOptions options = IslandOptions.getCrafting(); - item.setHasSentNotif(true); - - if (!options.isEnableCraftingNotifs()) return; - boolean shouldMakeSound = false; - if (options.isToastNotif()) { - client.getToastManager().addToast(new CraftingToast(item)); - shouldMakeSound = true; - } - if (options.isChatNotif()) { - sendChatNotif(item); - shouldMakeSound = true; - } - - if (shouldMakeSound) { - sendNotifSound(); - } - } - - private void sendChatNotif(CraftingItem item) { - Style darkGreenColor = Style.EMPTY.withColor(ChatFormatting.DARK_GREEN); - Component component = Component.literal("(").withStyle(darkGreenColor) - .append(item.getTypeIcon()) - .append(Component.literal(") ").withStyle(darkGreenColor)) - .append(item.getTitle()) - .append(Component.literal(" has finished crafting!").withStyle(darkGreenColor)); - ChatUtils.send(component); - } - - private void sendNotifSound() { - SimpleSoundInstance mcc = SimpleSoundInstance.forUI(IslandSoundEvents.UI_ACHIEVEMENT_RECEIVE, 1f, 1f); - Scheduler.schedule(5, (mc) -> { - mc.getSoundManager().play(mcc); - }); - } - - public static Component activeCraftsMessage() { - Component newLine = Component.literal("\n").withStyle(Style.EMPTY); - MutableComponent component = Component.literal("\nCRAFTING ITEMS:").withStyle(MCC_HUD_FONT); - component.append(newLine); - - int i = 0; - List itemList = CraftingItems.getItems(); - for (CraftingItem item : itemList) { - i++; - - long timeRemaining = item.getFinishesCrafting() - System.currentTimeMillis(); - String timeText; - ChatFormatting timeColor; - if (timeRemaining > 0) { - timeText = DurationFormatUtils.formatDuration(timeRemaining, "H'h' m'm' s's'"); - timeColor = ChatFormatting.RED; - } else { - timeText = "Complete"; - timeColor = ChatFormatting.DARK_GREEN; - } - - Component itemComponent = Component.literal(" ").withStyle(Style.EMPTY.withFont(Style.DEFAULT_FONT)) - .append(item.getTypeIcon()) - .append(" ") - .append(item.getTitle()) - .append(Component.literal(" - ").withStyle(ChatFormatting.DARK_GRAY)) - .append(Component.literal(timeText).withStyle(timeColor)); - - component.append(itemComponent); - if (i < itemList.size()) component.append(newLine); - } - return component; - } - - @Override - public void onEndTick(Minecraft client) { - tick++; - if (tick >= 20) { - update(client); - tick = 0; - } - } - - public static LiteralArgumentBuilder getDebugCommand() { - return literal("add_craft") - .then(argument("color", StringArgumentType.string()) - .then(argument("slot", IntegerArgumentType.integer()) - .then(argument("delay", IntegerArgumentType.integer()) - .executes(ctx -> { - if (!IslandOptions.getMisc().isDebugMode()) { - ctx.getSource().sendError(cantUseDebugError); - return 0; - } - String color = ctx.getArgument("color", String.class); - Integer slot = ctx.getArgument("slot", Integer.class); - Integer delay = ctx.getArgument("delay", Integer.class); - CraftingItems.addDebugItem(color, slot, delay); - return 1; - })))); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/MusicManager.java b/src/main/java/net/asodev/islandutils/modules/music/MusicManager.java deleted file mode 100644 index 88a4dbcb..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/MusicManager.java +++ /dev/null @@ -1,131 +0,0 @@ -package net.asodev.islandutils.modules.music; - -import net.asodev.islandutils.mixins.accessors.SoundEngineAccessor; -import net.asodev.islandutils.mixins.accessors.SoundManagerAccessor; -import net.asodev.islandutils.modules.music.modifiers.ClassicHitwMusic; -import net.asodev.islandutils.modules.music.modifiers.HighQualityMusic; -import net.asodev.islandutils.modules.music.modifiers.PreviousDynaballMusic; -import net.asodev.islandutils.modules.music.modifiers.TgttosDomeModifier; -import net.asodev.islandutils.modules.music.modifiers.TgttosDoubleTime; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.MCCSoundInstance; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.client.sounds.ChannelAccess; -import net.minecraft.client.sounds.SoundEngine; -import net.minecraft.client.sounds.SoundManager; -import net.minecraft.network.protocol.game.ClientboundSoundPacket; -import net.minecraft.network.protocol.game.ClientboundStopSoundPacket; -import net.minecraft.resources.ResourceLocation; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class MusicManager { - private static final int FADE_DURATION = 20; - private final static List modifiersList = new ArrayList<>(); - private static MCCSoundInstance currentlyPlaying = null; - - private static final List loopingTracks = List.of( - "music.global.parkour_warrior", - "music.global.battle_box", - "music.global.dynaball", - "music.global.hole_in_the_wall", - "music.global.sky_battle", - "music.global.tgttosawaf", - "music.global.overtime_loop_music" - ); - - public static void init() { - addModifier(new TgttosDoubleTime()); - addModifier(new TgttosDomeModifier()); - addModifier(new ClassicHitwMusic()); - addModifier(new PreviousDynaballMusic()); - addModifier(new HighQualityMusic()); - - ClientTickEvents.START_CLIENT_TICK.register((client) -> { - if (currentlyPlaying == null) return; - if (currentlyPlaying.isStopped()) { - client.getSoundManager().stop(currentlyPlaying); - } - }); - } - - private static boolean shouldIgnore(SoundInfo info) { - // ignore tracks that aren't the music tracks we wanna mess with - return !loopingTracks.contains(info.path().getPath()); - } - - public static void onMusicSoundPacket(ClientboundSoundPacket packet, Minecraft minecraft) { - startMusic(SoundInfo.fromPacket(packet)); - } - - public static void startMusic(SoundInfo info) { - if (shouldIgnore(info)) { - Minecraft.getInstance().getSoundManager().play(info.toSoundInstance()); - return; - } - - SoundInfo newSoundInfo = applyModifiers(info) - .withLooping(true); - - if (currentlyPlaying != null) { - if (newSoundInfo.path().equals(currentlyPlaying.location)) { - ChatUtils.debug("Cancelled the playing of " + info.path() + " because it was already playing."); - return; - } - currentlyPlaying.fade(FADE_DURATION); - } - - MCCSoundInstance instance = newSoundInfo.toSoundInstance(); - currentlyPlaying = instance; - Minecraft.getInstance().getSoundManager().play(instance); - } - - public static void onMusicStopPacket(ClientboundStopSoundPacket packet, Minecraft minecraft) { - ResourceLocation name = packet.getName(); - ResourceLocation modifiedName = applyModifiers(SoundInfo.fromLocation(name)).path(); - - SoundManager soundManager = Minecraft.getInstance().getSoundManager(); - for (SoundInstance instance : getActiveSoundInstances()) { - if (instance.getLocation().equals(name) || instance.getLocation().equals(modifiedName)) { - if (instance instanceof MCCSoundInstance mccSound) { - mccSound.fade(FADE_DURATION); - } else { - soundManager.stop(instance); - } - } - } - if (currentlyPlaying != null && currentlyPlaying.getLocation().equals(name)) { - currentlyPlaying = null; - } - } - - public static SoundInfo applyModifiers(SoundInfo info) { - SoundInfo modified = info; - - for (MusicModifier modifier : modifiersList) { - if (!modifier.isEnabled() || !modifier.shouldApply(info.path())) continue; - modified = modifier.apply(modified); - } - return modified; - } - - public static List getModifiers() { - return modifiersList; - } - - private static void addModifier(MusicModifier modifier) { - modifiersList.add(modifier); - } - - private static Set getActiveSoundInstances() { - SoundManager soundManager = Minecraft.getInstance().getSoundManager(); - SoundEngine engine = ((SoundManagerAccessor)soundManager).getSoundEngine(); - Map instanceToChannel = ((SoundEngineAccessor)engine).getInstanceToChannel(); - return instanceToChannel.keySet(); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/MusicModifier.java b/src/main/java/net/asodev/islandutils/modules/music/MusicModifier.java deleted file mode 100644 index 4499579e..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/MusicModifier.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.asodev.islandutils.modules.music; - -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; - -public abstract class MusicModifier { - private boolean isEnabled = defaultOption(); - private final String identifier; - private final Component name; - private final Component desc; - - public MusicModifier(String identifier) { - this.identifier = identifier; - this.name = Component.translatable("islandutils.music_modifier." + identifier); - this.desc = Component.translatableWithFallback("islandutils.music_modifier." + identifier + ".desc", ""); - } - - public abstract SoundInfo apply(SoundInfo info); - public abstract boolean shouldApply(ResourceLocation soundLocation); - - public boolean hasOption() { - return true; - } - public boolean defaultOption() { - return true; - } - - public final boolean isEnabled() { - return isEnabled; - } - public final void setEnabled(boolean enabled) { - isEnabled = enabled; - } - - public final String identifier() { - return identifier; - } - public final Component name() { - return name; - } - public Component desc() { - return desc; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/SoundInfo.java b/src/main/java/net/asodev/islandutils/modules/music/SoundInfo.java deleted file mode 100644 index 8f675e67..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/SoundInfo.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.asodev.islandutils.modules.music; - -import net.asodev.islandutils.util.MCCSoundInstance; -import net.minecraft.core.Holder; -import net.minecraft.network.protocol.game.ClientboundSoundPacket; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundSource; - -public record SoundInfo(ResourceLocation path, SoundSource category, double x, double y, double z, float volume, float pitch, long seed, boolean looping) { - - public SoundInfo withPath(ResourceLocation path) { - return new SoundInfo(path, category, x, y, z, volume, pitch, seed, looping); - } - - public SoundInfo withCategory(SoundSource category) { - return new SoundInfo(path, category, x, y, z, volume, pitch, seed, looping); - } - - public SoundInfo withPitch(float pitch) { - return new SoundInfo(path, category, x, y, z, volume, pitch, seed, looping); - } - - public SoundInfo withLooping(boolean looping) { - return new SoundInfo(path, category, x, y, z, volume, pitch, seed, looping); - } - - public static SoundInfo fromLocation(ResourceLocation location) { - return new SoundInfo(location, SoundSource.MASTER, 0.0, 0.0, 0.0, 1f, 1f, 0L, false); - } - public static SoundInfo fromPacket(ClientboundSoundPacket soundPacket) { - return new SoundInfo( - soundPacket.getSound().value().location(), - soundPacket.getSource(), - soundPacket.getX(), - soundPacket.getY(), - soundPacket.getZ(), - soundPacket.getVolume(), - soundPacket.getPitch(), - soundPacket.getSeed(), - false - ); - } - - public MCCSoundInstance toSoundInstance() { - return new MCCSoundInstance(this); - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/TrackMusicModifier.java b/src/main/java/net/asodev/islandutils/modules/music/TrackMusicModifier.java deleted file mode 100644 index 23611fdf..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/TrackMusicModifier.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.asodev.islandutils.modules.music; - -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; - -public abstract class TrackMusicModifier extends MusicModifier { - private String trackPath; - - public TrackMusicModifier(String trackPath, String identifier) { - super(identifier); - this.trackPath = trackPath; - } - - @Override - public boolean shouldApply(ResourceLocation soundLocation) { - return soundLocation.getPath().equals(trackPath) && shouldApply1(soundLocation); - } - - public boolean shouldApply1(ResourceLocation soundLocation) { - return true; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/modifiers/ClassicHitwMusic.java b/src/main/java/net/asodev/islandutils/modules/music/modifiers/ClassicHitwMusic.java deleted file mode 100644 index 15a545dc..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/modifiers/ClassicHitwMusic.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.asodev.islandutils.modules.music.modifiers; - -import net.asodev.islandutils.IslandUtils; -import net.asodev.islandutils.modules.music.SoundInfo; -import net.asodev.islandutils.modules.music.TrackMusicModifier; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.IslandSoundEvents; -import net.minecraft.ChatFormatting; -import net.minecraft.resources.ResourceLocation; - -import static net.minecraft.network.chat.Component.literal; - -public class ClassicHitwMusic extends TrackMusicModifier { - public ClassicHitwMusic(){ - super("music.global.hole_in_the_wall", "classic_hitw.music"); - } - - @Override - public SoundInfo apply(SoundInfo info) { - ChatUtils.send(literal("Now playing: ").withStyle(ChatFormatting.GREEN) - .append(literal("Spacewall - Taylor Grover").withStyle(ChatFormatting.AQUA)) - ); - return info.withPath(IslandSoundEvents.islandSound("island.music.classic_hitw")); - } - - @Override - public boolean hasOption() { - return false; - } - - @Override - public boolean shouldApply1(ResourceLocation soundLocation) { - return IslandOptions.getClassicHITW().isClassicHITWMusic(); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/modifiers/HighQualityMusic.java b/src/main/java/net/asodev/islandutils/modules/music/modifiers/HighQualityMusic.java deleted file mode 100644 index 3cfe8208..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/modifiers/HighQualityMusic.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.asodev.islandutils.modules.music.modifiers; - -import net.asodev.islandutils.modules.music.MusicModifier; -import net.asodev.islandutils.modules.music.SoundInfo; -import net.asodev.islandutils.modules.music.TrackMusicModifier; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.util.IslandSoundEvents; -import net.minecraft.resources.ResourceLocation; - -import java.util.Map; - -public class HighQualityMusic extends MusicModifier { - Map replacements = Map.of( - "music.global.parkour_warrior", Game.PARKOUR_WARRIOR_DOJO.getMusicLocation(), - "music.global.battle_box", Game.BATTLE_BOX.getMusicLocation(), - "music.global.hole_in_the_wall", Game.HITW.getMusicLocation(), - "music.global.rocket_spleef", Game.ROCKET_SPLEEF_RUSH.getMusicLocation(), - "music.global.sky_battle", Game.SKY_BATTLE.getMusicLocation(), - "music.global.tgttosawaf", Game.TGTTOS.getMusicLocation() - ); - - public HighQualityMusic(){ - super("global.hq"); - } - - @Override - public SoundInfo apply(SoundInfo info) { - ResourceLocation replacementPath = replacements.get(info.path().getPath()); - return replacementPath != null ? info.withPath(replacementPath) : info; - } - - @Override - public boolean defaultOption() { - return false; - } - - @Override - public boolean shouldApply(ResourceLocation soundLocation) { - return true; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/modifiers/PreviousDynaballMusic.java b/src/main/java/net/asodev/islandutils/modules/music/modifiers/PreviousDynaballMusic.java deleted file mode 100644 index 87ef42fa..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/modifiers/PreviousDynaballMusic.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.asodev.islandutils.modules.music.modifiers; - -import net.asodev.islandutils.modules.music.SoundInfo; -import net.asodev.islandutils.modules.music.TrackMusicModifier; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.IslandSoundEvents; -import net.minecraft.resources.ResourceLocation; - -public class PreviousDynaballMusic extends TrackMusicModifier { - public PreviousDynaballMusic(){ - super("music.global.dynaball", "dynaball.old_music"); - } - - @Override - public boolean defaultOption() { - return false; - } - - @Override - public SoundInfo apply(SoundInfo info) { - return info.withPath(IslandSoundEvents.islandSound("island.music.dynaball")); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/modifiers/TgttosDomeModifier.java b/src/main/java/net/asodev/islandutils/modules/music/modifiers/TgttosDomeModifier.java deleted file mode 100644 index fde68276..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/modifiers/TgttosDomeModifier.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.asodev.islandutils.modules.music.modifiers; - -import net.asodev.islandutils.modules.music.SoundInfo; -import net.asodev.islandutils.modules.music.TrackMusicModifier; -import net.asodev.islandutils.state.MccIslandState; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; - -public class TgttosDomeModifier extends TrackMusicModifier { - public TgttosDomeModifier(){ - super("music.global.tgttosawaf", "tgttos.dome_modifier"); - } - - @Override - public SoundInfo apply(SoundInfo info) { - return info.withPath(ResourceLocation.fromNamespaceAndPath("island", "island.music.to_the_dome")); - } - - @Override - public boolean shouldApply1(ResourceLocation soundLocation) { - return MccIslandState.getModifier().equals("TO THE DOME"); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/music/modifiers/TgttosDoubleTime.java b/src/main/java/net/asodev/islandutils/modules/music/modifiers/TgttosDoubleTime.java deleted file mode 100644 index d399317f..00000000 --- a/src/main/java/net/asodev/islandutils/modules/music/modifiers/TgttosDoubleTime.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.asodev.islandutils.modules.music.modifiers; - -import net.asodev.islandutils.modules.music.MusicModifier; -import net.asodev.islandutils.modules.music.SoundInfo; -import net.asodev.islandutils.modules.music.TrackMusicModifier; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.MCCSoundInstance; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; - -public class TgttosDoubleTime extends TrackMusicModifier { - public TgttosDoubleTime(){ - super("music.global.tgttosawaf", "tgttos.double_time"); - } - - @Override - public SoundInfo apply(SoundInfo info) { - return info.withPitch(1.2f); - } - - @Override - public boolean shouldApply1(ResourceLocation soundLocation) { - return MccIslandState.getModifier().equals("DOUBLE TIME"); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/plobby/PlobbyFeatures.java b/src/main/java/net/asodev/islandutils/modules/plobby/PlobbyFeatures.java deleted file mode 100644 index 480c98b7..00000000 --- a/src/main/java/net/asodev/islandutils/modules/plobby/PlobbyFeatures.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.asodev.islandutils.modules.plobby; - -import net.asodev.islandutils.IslandUtilsClient; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.Sidebar; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.minecraft.network.chat.Component; - -import java.util.regex.Pattern; - -public class PlobbyFeatures { - public static void registerEvents() { - ClientTickEvents.END_CLIENT_TICK.register(client -> { - if (!MccIslandState.isOnline()) return; - if (!IslandUtilsClient.openPlobbyKey.consumeClick() || client.player == null) return; - client.player.connection.sendCommand("plobby"); // Do /plobby to open the plobby menu - }); - } - - private static final Pattern plobbySidebarLine = Pattern.compile("PLOBBY.\\(."); - public static boolean isInPlobby() { - Component sidebarName = Sidebar.getSidebarName(); - if (!MccIslandState.isOnline() || sidebarName == null) return false; - - int lineNumber = Sidebar.findLine((line) -> plobbySidebarLine.matcher(line.getString()).find()); - boolean nameContainsPlobby = sidebarName.getString().contains("(Plobby)"); - return lineNumber != -1 || nameContainsPlobby; - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/plobby/PlobbyJoinCodeCopy.java b/src/main/java/net/asodev/islandutils/modules/plobby/PlobbyJoinCodeCopy.java deleted file mode 100644 index 7d80e4ec..00000000 --- a/src/main/java/net/asodev/islandutils/modules/plobby/PlobbyJoinCodeCopy.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.asodev.islandutils.modules.plobby; - -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.Utils; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.world.item.ItemStack; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class PlobbyJoinCodeCopy { - public static long lastCopy = 0; - private static final Component copiedMessage = Component.literal("Copied code to clipboard!") - .withStyle(Style.EMPTY.withColor(ChatUtils.parseColor("#ffff00"))); - - public static void register() { - IslandUtilsEvents.CHAT_MESSAGE.register((state, modify) -> { - if ((System.currentTimeMillis() - lastCopy) > 5000) return; // If we copied the code less than 5s ago - modify.replace(copiedMessage); // Replace with the copy success message - lastCopy = 0; // Reset the copy time - }); - } - - private final static Pattern codePattern = Pattern.compile(".•.([A-Za-z]{2}\\d{4})"); - public static String getJoinCodeFromItem(ItemStack item) { - List lores = Utils.getLores(item); - for (Component lore : lores) { - String loreString = lore.getString(); - Matcher matcher = codePattern.matcher(loreString); - if (!matcher.find()) continue; - return matcher.group(1); - } - return null; - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/scavenging/Scavenging.java b/src/main/java/net/asodev/islandutils/modules/scavenging/Scavenging.java deleted file mode 100644 index 5996f65a..00000000 --- a/src/main/java/net/asodev/islandutils/modules/scavenging/Scavenging.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.asodev.islandutils.modules.scavenging; - -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.util.Utils; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.gui.screens.inventory.ContainerScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.Container; -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; - -import java.util.Collection; -import java.util.List; - -public class Scavenging { - // the total width of the island menu - private static final int GUI_TOTAL_WIDTH = 176; - // the width of the menu body - private static final int GUI_BODY_WIDTH = 164; - // the offset between the end of the scavenging icons and the right-side of the body - private static final int SCAVENGING_ICON_INNER_OFFSET = 24; - - private static Component titleComponent; - private static ScavengingItemHandler dustHandler; - private static ScavengingItemHandler silverHandler; - private static ScavengingItemHandler coinHandler; - - public static boolean isScavengingMenuOrDisabled(AbstractContainerScreen screen) { - if (!IslandOptions.getMisc().isSilverPreview()) return false; - if (titleComponent == null) return false; - return screen.getTitle().contains(titleComponent); - } - public static void renderSilverTotal(ScavengingTotalList silverTotal, GuiGraphics guiGraphics) { - Minecraft minecraft = Minecraft.getInstance(); - if (!(minecraft.screen instanceof ContainerScreen screen)) return; - - int bgX = (screen.width - GUI_TOTAL_WIDTH) / 2; - int x = bgX + (GUI_TOTAL_WIDTH - SCAVENGING_ICON_INNER_OFFSET); - - Collection totals = silverTotal.totals.values(); - for (ScavengingTotal total : totals) { - if (total.amount() <= 0L) continue; - - x -= total.handler().renderTotal(guiGraphics, total.amount(), x); - x -= 5; // gap - } - } - - public static ScavengingTotalList getSilverTotal(ChestMenu menu) { - ScavengingTotalList list = new ScavengingTotalList(); - - Container container = menu.getContainer(); - applyItemRow(list, container, 11, 15); - applyItemRow(list, container, 20, 25); - return list; - } - private static void applyItemRow(ScavengingTotalList list, Container container, int min, int max) { - for (int i = min; i <= max; i++) { - ItemStack item = container.getItem(i); - if (item.is(Items.AIR)) continue; - applyItems(item, list); - } - } - - public static void applyItems(ItemStack item, ScavengingTotalList list) { - List lores = Utils.getLores(item); - if (lores == null) return; - - for (Component line : lores) { - if (silverHandler != null) list.apply(silverHandler.checkLine(line)); - if (dustHandler != null) list.apply(dustHandler.checkLine(line)); - if (coinHandler != null) list.apply(coinHandler.checkLine(line)); - } - } - - public static void setDustCharacter(String dustCharacter) { - dustHandler = new ScavengingItemHandler("dust", dustCharacter); - } - public static void setSilverCharacter(String silverCharacter) { - silverHandler = new ScavengingItemHandler("silver", silverCharacter); - } - public static void setCoinCharacter(String coinCharacter) { - coinHandler = new ScavengingItemHandler("coin", coinCharacter); - } - - public static void setTitleCharacter(String titleCharacter) { - Scavenging.titleComponent = Component.literal(titleCharacter).withStyle( - Style.EMPTY.withColor(ChatFormatting.WHITE).withFont(ResourceLocation.fromNamespaceAndPath("mcc", "chest_backgrounds")) - ); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingItemHandler.java b/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingItemHandler.java deleted file mode 100644 index 5eedfbe2..00000000 --- a/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingItemHandler.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.asodev.islandutils.modules.scavenging; - -import net.asodev.islandutils.mixins.accessors.ContainerScreenAccessor; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.gui.screens.inventory.ContainerScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.world.inventory.ChestMenu; - -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static net.asodev.islandutils.modules.cosmetics.CosmeticState.MCC_ICONS; -import static net.asodev.islandutils.util.ChatUtils.iconsFontStyle; - -public class ScavengingItemHandler { - private static final int MENU_WIDTH = 176; - private static final int CHEST_MENU_HEIGHT = 222; - - private final String character; - private final Pattern pattern; - private final String name; - private final ScavengingTotal total; - - public ScavengingItemHandler(String item, String character) { - this(item, character, character); - } - public ScavengingItemHandler(String item, String character, String detectString) { - this.character = character; - this.pattern = Pattern.compile("(\\d+).*" + detectString); - this.total = new ScavengingTotal(item, 0L, this); - this.name = item; - } - - public int renderTotal(GuiGraphics guiGraphics, Long total, int x) { - Minecraft minecraft = Minecraft.getInstance(); - Font font = minecraft.font; - if (!(minecraft.screen instanceof ContainerScreen screen)) return 0; - - Component silverComponent = Component.literal(String.valueOf(total)) - .append(Component.literal("_").withStyle(iconsFontStyle)) - .append(Component.literal(character).withStyle(Style.EMPTY.withFont(MCC_ICONS))); - int width = font.width(silverComponent); - - x -= width; - - // 25 = y offset between label y & top of menu - int topPos = ((screen.height - CHEST_MENU_HEIGHT) / 2) - 25; - // 152 = y pos of top of footer - // 20 = y pos from top of footer to top of button - // 5 = bottom padding - int y = topPos + 152 + 20 - 5; - - guiGraphics.pose().pushPose(); - guiGraphics.pose().translate(0, 0, 105); // z-index: 105 - guiGraphics.drawString(font, silverComponent, x, y, 16777215, false); - guiGraphics.pose().popPose(); - - return width; - } - - public ScavengingTotal checkLine(Component line) { - String content = line.getString().replace(",", ""); - Matcher matcher = pattern.matcher(content); - if (!matcher.find()) return total; - - String amountText = matcher.group(1); - try { - long amount = Long.parseLong(amountText); - return total.create(amount); - } catch (Exception ignored) {} - return total; - } - - public String getCharacter() { - return character; - } - public Pattern getPattern() { - return pattern; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ScavengingItemHandler that = (ScavengingItemHandler) o; - return Objects.equals(name, that.name); - } - @Override - public int hashCode() { - return Objects.hash(name); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingTotal.java b/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingTotal.java deleted file mode 100644 index 4e37f27d..00000000 --- a/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingTotal.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.asodev.islandutils.modules.scavenging; - -public record ScavengingTotal(String item, Long amount, ScavengingItemHandler handler) { - public ScavengingTotal apply(ScavengingTotal total) { - return new ScavengingTotal(this.item, this.amount + total.amount, this.handler); - } - - public ScavengingTotal create(Long amount) { - return new ScavengingTotal(this.item, amount, this.handler); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingTotalList.java b/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingTotalList.java deleted file mode 100644 index 7b2b4e2e..00000000 --- a/src/main/java/net/asodev/islandutils/modules/scavenging/ScavengingTotalList.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.asodev.islandutils.modules.scavenging; - -import java.util.HashMap; -import java.util.Map; - -public class ScavengingTotalList { - - Map totals = new HashMap<>(); - - public void apply(ScavengingTotal newTotal) { - ScavengingTotal currentTotal = totals.get(newTotal.handler()); - - ScavengingTotal addedTotal = newTotal; - if (currentTotal != null) { - addedTotal = currentTotal.apply(newTotal); - } - - - totals.put(newTotal.handler(), addedTotal); - } - -} diff --git a/src/main/java/net/asodev/islandutils/modules/splits/LevelSplits.java b/src/main/java/net/asodev/islandutils/modules/splits/LevelSplits.java deleted file mode 100644 index cfcd24a4..00000000 --- a/src/main/java/net/asodev/islandutils/modules/splits/LevelSplits.java +++ /dev/null @@ -1,104 +0,0 @@ -package net.asodev.islandutils.modules.splits; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.util.ChatUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class LevelSplits { - - private String name; - private Long expires = null; - private Map splits = new HashMap<>(); - private Map levelNames = new HashMap<>(); - - public LevelSplits(String name) { - this.name = name; - } - public LevelSplits(JsonObject json) { - name = json.get("name").getAsString(); - JsonElement expiresElement = json.get("expires"); - if (!expiresElement.isJsonNull()) - expires = expiresElement.getAsLong(); - - Map splitMap = json.getAsJsonObject("splits").asMap(); - splitMap.forEach((hash, element) -> splits.put(hash, Split.fromJson(element))); - - Map splitNames = json.getAsJsonObject("names").asMap(); - splitNames.forEach((hash, element) -> levelNames.put(hash, element.getAsString())); - } - - public void saveSplit(String uid, String name, Long time) { - Split currentSplit = splits.get(uid); - if (currentSplit == null) { - currentSplit = new Split(time, time.doubleValue(), List.of(time)); - } else { - currentSplit = currentSplit.addTime(time); - } - - splits.put(uid, currentSplit); - levelNames.put(uid, name); - ChatUtils.debug("LevelSplits - Time (" + time + "ms) was saved with uid: " + uid); - SplitManager.saveAsync(); - } - public Double getSplit(String level) { - if (!splits.containsKey(level)) return null; - SplitType type = IslandOptions.getSplits().getSaveMode(); - double value = 0.0; - switch (type) { - case BEST -> value = splits.get(level).best(); - case AVG -> value = splits.get(level).avg(); - } - return value / 1000d; - } - - public JsonObject toJson() { - JsonObject object = new JsonObject(); - object.addProperty("name", name); - object.addProperty("expires", expires); - - JsonObject splitObject = new JsonObject(); - this.splits.forEach((hash, split) -> splitObject.add(hash, split.toJson())); - object.add("splits", splitObject); - - JsonObject levelNames = new JsonObject(); - this.levelNames.forEach(levelNames::addProperty); - object.add("names", levelNames); - - return object; - } - - public String getName() { - return name; - } - public Long getExpires() { - return expires; - } - public void setExpires(Long expires) { - this.expires = expires; - } - - public record Split(Long best, Double avg, List times){ - public Split addTime(Long time) { - List newList = new ArrayList<>(times); - newList.add(time); - double newAvg = newList.stream().mapToDouble(d -> d).average().orElse(0.0); - Long newBest = time < best ? time : best; - return new Split(newBest, newAvg, Collections.unmodifiableList(newList)); - } - - public static Split fromJson(JsonElement json) { - return new Gson().fromJson(json, Split.class); - } - public JsonElement toJson() { - return new Gson().toJsonTree(this); - } - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/splits/LevelTimer.java b/src/main/java/net/asodev/islandutils/modules/splits/LevelTimer.java deleted file mode 100644 index 561ab8e2..00000000 --- a/src/main/java/net/asodev/islandutils/modules/splits/LevelTimer.java +++ /dev/null @@ -1,204 +0,0 @@ -package net.asodev.islandutils.modules.splits; - -import net.asodev.islandutils.modules.splits.ui.DojoSplitUI; -import net.asodev.islandutils.modules.splits.ui.SplitUI; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.options.categories.SplitsCategory; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.asodev.islandutils.util.ChatUtils; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; -import net.minecraft.network.chat.TextColor; -import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; -import net.minecraft.network.protocol.game.ClientboundSoundPacket; -import net.minecraft.resources.ResourceLocation; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class LevelTimer { - private static final Pattern channelTitlePattern = Pattern.compile("\\[(.*)]"); - - private SplitUI splitUI = null; - private final LevelSplits splits; - private Long lastSplitTimestamp = System.currentTimeMillis(); - - private String levelName = "M1-1"; - private String levelUid = ""; - - private boolean isBetween = true; // If the player is inbetween levels; - public final SplitsCategory options = IslandOptions.getSplits(); - - public LevelTimer(LevelSplits splits) { - this.splits = splits; - if (splits != null && splits.getExpires() == null) { - splits.setExpires(SplitManager.getCurrentCourseExpiry()); - } - if (options.isShowTimer()) { - this.splitUI = new DojoSplitUI(this); - } - } - - public void handleSubtitle(ClientboundSetSubtitleTextPacket subtitle, CallbackInfo ci) { - Component component = subtitle.text(); - String string = component.getString(); - if (string.contains(medalCharacter) && string.length() < 4) { - modifyMedalTitle(subtitle, ci); - } - if (string.startsWith("[")) { - Matcher matcher = channelTitlePattern.matcher(string); - if (!matcher.find()) return; - // This title is sent 1.5s AFTER the level starts, so we need to compensate - lastSplitTimestamp = System.currentTimeMillis() - 1500; - levelName = matcher.group(1); - isBetween = false; - - StringBuilder hashString = new StringBuilder(); - for (Component sibling : component.getSiblings()) { - TextColor color = sibling.getStyle().getColor(); - if (color != null) { - hashString.append(color); - } - } - hashString.append(levelName); - levelUid = hashString.toString(); - ChatUtils.debug("Detected level with id: " + levelUid); - } - } - public void modifyMedalTitle(ClientboundSetSubtitleTextPacket subtitle, CallbackInfo ci) { - Component component = subtitle.text(); - MutableComponent component1 = component.copy(); - if (options.isShowSplitImprovements()) { - component1.append(getSplitImprovementComponent()); - } - Minecraft.getInstance().gui.setSubtitle(component1); - ci.cancel(); - - saveSplit(); - lastSplitTimestamp = System.currentTimeMillis(); - isBetween = true; - } - public void saveSplit() { - if (splits != null) { - sendSplitCompeteMessage(); - - Long millis = getCurrentSplitTimeMilis(); - splits.saveSplit(levelUid, levelName, millis); - } - } - public void sendSplitCompeteMessage() { - if (!options.isSendSplitTime()) return; - String time = String.format("%.3fs", getCurrentSplitTime()); - Style tickFont = Style.EMPTY.withFont(ResourceLocation.fromNamespaceAndPath("island", "icons")).withColor(ChatFormatting.WHITE); - - MutableComponent component = Component.literal("[").withStyle(ChatFormatting.GREEN) - .append(Component.literal("\ue009").withStyle(tickFont)) - .append("] " + levelName + " complete in: ") - .append(Component.literal(time).withStyle(Style.EMPTY.withColor(ChatFormatting.WHITE))); - if (options.isShowSplitImprovements()) { - component.append(Component.empty().withStyle(ChatFormatting.WHITE).append(getSplitImprovementComponent())); - } - ChatUtils.send(component); - } - private Component getSplitImprovementComponent() { - Double splitImprovement = getSplitImprovement(); - if (splitImprovement == null) splitImprovement = 0d; - - String formattedTime = String.format("%.2f", splitImprovement); - ChatFormatting color; - Component icon; - if (splitImprovement > 0) { - color = ChatFormatting.RED; - icon = splitDownComponent.copy().withStyle(ChatFormatting.WHITE); - formattedTime = "+" + formattedTime; - } else if (splitImprovement < 0) { - color = ChatFormatting.GREEN; - icon = splitUpComponent.copy().withStyle(ChatFormatting.WHITE); - } else { - color = ChatFormatting.YELLOW; - icon = Component.literal("-").withStyle(color); - } - - return Component.literal(" (").withStyle(Style.EMPTY) - .append(icon) - .append(Component.literal(" " + formattedTime).withStyle(color)) - .append(Component.literal(")").withStyle(Style.EMPTY)); - } - - public Long getCurrentSplitTimeMilis() { - return (System.currentTimeMillis() - lastSplitTimestamp); - } - public double getCurrentSplitTime() { - return getCurrentSplitTimeMilis() / 1000d; - } - public Double getSplitImprovement() { - double currentSplitTime = getCurrentSplitTime(); - if (splits == null) { - return null; - } else { - Double split = splits.getSplit(levelUid); - if (split == null) return null; - return currentSplitTime - split; - } - } - - public static void onSound(ClientboundSoundPacket clientboundSoundPacket) { - if (!IslandOptions.getSplits().isEnablePkwSplits()) return; - ResourceLocation soundLoc = clientboundSoundPacket.getSound().value().location(); - String path = soundLoc.getPath(); - boolean isRoundEnd = path.equals("games.global.timer.round_end"); - if (path.contains("games.parkour_warrior.mode_swap") || - path.contains("games.parkour_warrior.restart_course") || - isRoundEnd || - path.equals("ui.queue_teleport")) { - // Stop split - LevelTimer currentInstance = getInstance(); - if (currentInstance != null && isRoundEnd) { - currentInstance.saveSplit(); - } - setInstance(null); - ChatUtils.debug("LevelTimer - Ended timer"); - } else if (path.equals("games.global.countdown.go")) { - LevelSplits splits = null; - if (MccIslandState.getGame() == Game.PARKOUR_WARRIOR_DOJO) { - splits = SplitManager.getCourseSplits(MccIslandState.getMap()); - } - setInstance(new LevelTimer(splits)); - ChatUtils.debug("LevelTimer - Started timer!"); - } - } - - public static void updateFromConfig(SplitsCategory options) { - if (!options.isEnablePkwSplits()) setInstance(null); - } - - public SplitUI getUI() { - return splitUI; - } - public String getLevelName() { - return levelName; - } - public boolean isBetween() { - return isBetween; - } - - // Instance stuff - private static LevelTimer instance; - - public static LevelTimer getInstance() { - return instance; - } - public static void setInstance(LevelTimer instance) { - LevelTimer.instance = instance; - } - - // Font stuff - public static String medalCharacter = ""; - public static Component splitUpComponent = Component.empty(); - public static Component splitDownComponent = Component.empty(); -} diff --git a/src/main/java/net/asodev/islandutils/modules/splits/SplitManager.java b/src/main/java/net/asodev/islandutils/modules/splits/SplitManager.java deleted file mode 100644 index 90e0aefc..00000000 --- a/src/main/java/net/asodev/islandutils/modules/splits/SplitManager.java +++ /dev/null @@ -1,101 +0,0 @@ -package net.asodev.islandutils.modules.splits; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.util.ChatUtils; -import net.asodev.islandutils.util.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import static net.asodev.islandutils.util.resourcepack.ResourcePackOptions.islandFolder; - -public class SplitManager { - private static Logger logger = LoggerFactory.getLogger(SplitManager.class); - private static final File file = new File(islandFolder + "/splits.json"); - - private static final Map courseSplits = new HashMap<>(); - private static Long currentCourseExpiry = null; - - public static void init() { - SplitManager.load(); - - IslandUtilsEvents.GAME_CHANGE.register((game) -> { - if (game != Game.PARKOUR_WARRIOR_DOJO) LevelTimer.setInstance(null); - }); - } - - public static LevelSplits getCourseSplits(String courseName) { - String name = courseName.toLowerCase().contains("daily challenge") ? "daily" : courseName; - LevelSplits levelSplits = courseSplits.get(name); - - if (levelSplits == null || (levelSplits.getExpires() != null && System.currentTimeMillis() >= levelSplits.getExpires())) { - levelSplits = new LevelSplits(name); - levelSplits.setExpires(currentCourseExpiry); - courseSplits.put(name, levelSplits); - ChatUtils.debug("LevelTimer - Created splits for: " + name); - } else { - ChatUtils.debug("SplitManager - Found splits for: " + name); - } - return levelSplits; - } - - public static void clearSplits() { - courseSplits.clear(); - saveAsync(); - } - - public static void saveAsync() { - Utils.savingQueue.submit(() -> { - try { - save(); - logger.info("Saved splits!"); - } catch (Exception e) { - logger.error("Failed to save splits", e); - } - }); - } - - public static void save() throws IOException { - JsonObject object = new JsonObject(); - JsonArray array = new JsonArray(); - for (Map.Entry split : courseSplits.entrySet()) { - array.add(split.getValue().toJson()); - } - - object.add("splits", array); - object.addProperty("savedAt", System.currentTimeMillis()); - object.addProperty("version", 1); - Utils.writeFile(file, object.toString()); - } - private static void load() { - try { - String string = Utils.readFile(file); - if (string == null) return; - - JsonObject object = new Gson().fromJson(string, JsonObject.class); - for (JsonElement element : object.getAsJsonArray("splits").asList()) { - LevelSplits splits = new LevelSplits(element.getAsJsonObject()); - courseSplits.put(splits.getName(), splits); - } - } catch (Exception e) { - logger.error("Failed to load splits", e); - } - } - - public static Long getCurrentCourseExpiry() { - return currentCourseExpiry; - } - - public static void setCurrentCourseExpiry(Long currentCourseExpiry) { - SplitManager.currentCourseExpiry = currentCourseExpiry; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/splits/SplitType.java b/src/main/java/net/asodev/islandutils/modules/splits/SplitType.java deleted file mode 100644 index ce0fa924..00000000 --- a/src/main/java/net/asodev/islandutils/modules/splits/SplitType.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.asodev.islandutils.modules.splits; - -import dev.isxander.yacl3.api.NameableEnum; -import net.minecraft.network.chat.Component; - -public enum SplitType implements NameableEnum { - BEST, - AVG; - - - @Override - public Component getDisplayName() { - return Component.translatable("islandutils.splittype." + this.name().toLowerCase()); - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/splits/ui/DojoSplitUI.java b/src/main/java/net/asodev/islandutils/modules/splits/ui/DojoSplitUI.java deleted file mode 100644 index 855c9bfe..00000000 --- a/src/main/java/net/asodev/islandutils/modules/splits/ui/DojoSplitUI.java +++ /dev/null @@ -1,85 +0,0 @@ -package net.asodev.islandutils.modules.splits.ui; - -import com.mojang.blaze3d.systems.RenderSystem; -import net.asodev.islandutils.modules.splits.LevelTimer; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; - -public class DojoSplitUI implements SplitUI { - private static final ResourceLocation BAR_TEXTURE = ResourceLocation.fromNamespaceAndPath("island", "pkw_splits"); - private static final int MCC_BAR_WIDTH = 130; - public static Style MCC_HUD_STYLE = Style.EMPTY.withFont(ResourceLocation.fromNamespaceAndPath("mcc", "hud")); - - private LevelTimer timer; - - public DojoSplitUI(LevelTimer timer) { - this.timer = timer; - } - - @Override - public void render(GuiGraphics guiGraphics, int bossBars) { - int x = (guiGraphics.guiWidth() / 2) - (MCC_BAR_WIDTH / 2); - int y = Double.valueOf((bossBars * 18.5)).intValue(); - guiGraphics.blitSprite(RenderType::guiTextured, BAR_TEXTURE, x, y, this.width(), this.height()); - - renderLevelName(guiGraphics, x, y); - renderSplitTime(guiGraphics, x, y); - if (timer.options.isShowSplitImprovements()) { - renderSplitImprovement(guiGraphics, x, y); - } - } - public void renderLevelName(GuiGraphics guiGraphics, int x, int y) { - Font font = Minecraft.getInstance().font; // Minecraft is incapable of getting this itself - Component levelName = Component.literal(timer.getLevelName()).withStyle(MCC_HUD_STYLE); - int LEVEL_NAME_WIDTH = 25; // Width of the dark area for the level name - - int txoff = (LEVEL_NAME_WIDTH / 2) - (font.width(levelName) / 2); // Offset needed to center the level name - int tx = x + txoff + 1; // The X coordinate to render the level name - int ty = y + 2; // The Y coordinate to render the level name - guiGraphics.drawString(font, levelName, tx, ty, 16777215 | 255 << 24, true); - } - - public void renderSplitTime(GuiGraphics guiGraphics, int x, int y) { - Font font = Minecraft.getInstance().font; - String formattedTime = String.format("%.3f", timer.getCurrentSplitTime()); - Component splitTime = Component.literal(formattedTime); - int tx = x + this.width() - font.width(splitTime) - 2; - int ty = y + 2; - guiGraphics.drawString(font, splitTime, tx, ty, 16777215 | 255 << 24, true); - } - - public void renderSplitImprovement(GuiGraphics guiGraphics, int x, int y) { - if (timer.isBetween()) return; - - Double splitImprovement = timer.getSplitImprovement(); - if (splitImprovement == null) return; - if (splitImprovement < timer.options.getShowTimerImprovementAt()) return; - - String formattedTime = String.format("%.2fs", splitImprovement); - ChatFormatting color = ChatFormatting.GREEN; - if (splitImprovement > 0) { - color = ChatFormatting.RED; - formattedTime = "+" + formattedTime; - } - - Font font = Minecraft.getInstance().font; - Component improvementTime = Component.literal(formattedTime).withStyle(MCC_HUD_STYLE.withColor(color)); - int tx = x + 12 + (this.width() / 2) - (font.width(improvementTime) / 2); - int ty = y + 2; - guiGraphics.drawString(font, improvementTime, tx, ty, 16777215 | 255 << 24, true); - } - - - private int width() { - return 130; - } - private int height() { - return 12; - } -} diff --git a/src/main/java/net/asodev/islandutils/modules/splits/ui/SplitUI.java b/src/main/java/net/asodev/islandutils/modules/splits/ui/SplitUI.java deleted file mode 100644 index 5422b9f5..00000000 --- a/src/main/java/net/asodev/islandutils/modules/splits/ui/SplitUI.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.asodev.islandutils.modules.splits.ui; - -import net.asodev.islandutils.modules.splits.LevelTimer; -import net.asodev.islandutils.util.ChatUtils; -import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; -import net.minecraft.client.gui.GuiGraphics; - -public interface SplitUI { - void render(GuiGraphics guiGraphics, int bossBars); - - public static void renderInstance(GuiGraphics guiGraphics, int bossBars) { - LevelTimer instance = LevelTimer.getInstance(); - if (instance != null && instance.getUI() != null) instance.getUI().render(guiGraphics, bossBars); - } - - static void setupFallbackRenderer() { - ChatUtils.debug("Setup fallback renderer for SplitUI"); - HudRenderCallback.EVENT.register((drawContext, tickDelta) -> { - renderInstance(drawContext, 1); - }); - } -} diff --git a/src/main/java/net/asodev/islandutils/options/IslandOptions.java b/src/main/java/net/asodev/islandutils/options/IslandOptions.java deleted file mode 100644 index 317f50c5..00000000 --- a/src/main/java/net/asodev/islandutils/options/IslandOptions.java +++ /dev/null @@ -1,132 +0,0 @@ -package net.asodev.islandutils.options; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.YetAnotherConfigLib; -import net.asodev.islandutils.discord.DiscordPresenceUpdator; -import net.asodev.islandutils.modules.splits.LevelTimer; -import net.asodev.islandutils.options.categories.ClassicOptions; -import net.asodev.islandutils.options.categories.CosmeticsOptions; -import net.asodev.islandutils.options.categories.CraftingOptions; -import net.asodev.islandutils.options.categories.DiscordOptions; -import net.asodev.islandutils.options.categories.MiscOptions; -import net.asodev.islandutils.options.categories.MusicOptions; -import net.asodev.islandutils.options.categories.OptionsCategory; -import net.asodev.islandutils.options.categories.PlobbyOptions; -import net.asodev.islandutils.options.categories.SplitsCategory; -import net.asodev.islandutils.options.saving.IslandUtilsSaveHandler; -import net.asodev.islandutils.util.Utils; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class IslandOptions { - private static final Logger LOGGER = LoggerFactory.getLogger(IslandOptions.class); - private static final File configFile = new File(FabricLoader.getInstance().getConfigDir().toFile(), "islandutils.json"); - private static final IslandUtilsSaveHandler saveHandler = new IslandUtilsSaveHandler(); - private static final List categories = new ArrayList<>(); - private static final MusicOptions music = new MusicOptions(); - private static final CosmeticsOptions cosmetics = new CosmeticsOptions(); - private static final DiscordOptions discord = new DiscordOptions(); - private static final ClassicOptions classicHITW = new ClassicOptions(); - private static final CraftingOptions crafting = new CraftingOptions(); - private static final SplitsCategory splits = new SplitsCategory(); - private static final PlobbyOptions plobby = new PlobbyOptions(); - private static final MiscOptions misc = new MiscOptions(); - - public static void init() { - categories.add(music); - categories.add(cosmetics); - categories.add(discord); - categories.add(classicHITW); - categories.add(crafting); - categories.add(splits); - categories.add(plobby); - categories.add(misc); - load(); - } - - private static void load() { - if (!configFile.exists()) { return; } - JsonObject object; - try { - String s = Utils.readFile(configFile); - object = (new Gson()).fromJson(s, JsonObject.class); - } catch (Exception e) { - LOGGER.error("Failed to load IslandUtils config file", e); - return; - } - - for (OptionsCategory category : categories) { - saveHandler.load(category, object); - } - } - - public static void save() { - DiscordPresenceUpdator.updateFromConfig(discord); - LevelTimer.updateFromConfig(splits); - - JsonObject object = new JsonObject(); - for (OptionsCategory category : categories) { - try { - saveHandler.save(category, object); - } catch (IllegalAccessException e) { - LOGGER.error("Failed to save category: " + category, e); - return; - } - } - - try { - Utils.writeFile(configFile, object.toString()); - } catch (IOException e) { - LOGGER.error("Failed to save IslandUtils options!", e); - } - - LOGGER.info("Saved IslandUtils Options!"); - } - public static MusicOptions getMusic() { - return music; - } - public static CosmeticsOptions getCosmetics() { - return cosmetics; - } - public static DiscordOptions getDiscord() { - return discord; - } - public static ClassicOptions getClassicHITW() { - return classicHITW; - } - public static CraftingOptions getCrafting() { - return crafting; - } - public static SplitsCategory getSplits() { - return splits; - } - public static PlobbyOptions getPlobby() { - return plobby; - } - public static MiscOptions getMisc() { - return misc; - } - - - public static Screen getScreen(Screen parent) { - List yaclCategories = categories.stream().map(OptionsCategory::getCategory).toList(); - - return YetAnotherConfigLib.createBuilder() - .title(Component.literal("IslandUtils Options")) - .save(IslandOptions::save) - .categories(yaclCategories) - .build() - .generateScreen(parent); - } - -} diff --git a/src/main/java/net/asodev/islandutils/options/ModMenuIntegration.java b/src/main/java/net/asodev/islandutils/options/ModMenuIntegration.java deleted file mode 100644 index 00e8c48c..00000000 --- a/src/main/java/net/asodev/islandutils/options/ModMenuIntegration.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.asodev.islandutils.options; - -import com.terraformersmc.modmenu.api.ConfigScreenFactory; -import com.terraformersmc.modmenu.api.ModMenuApi; - -public class ModMenuIntegration implements ModMenuApi { - - @Override - public ConfigScreenFactory getModConfigScreenFactory() { - return IslandOptions::getScreen; - } - -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/ClassicOptions.java b/src/main/java/net/asodev/islandutils/options/categories/ClassicOptions.java deleted file mode 100644 index 92f718e2..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/ClassicOptions.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; -import net.asodev.islandutils.options.saving.Ignore; -import net.minecraft.network.chat.Component; - -public class ClassicOptions implements OptionsCategory { - @Ignore - private static final ClassicOptions defaults = new ClassicOptions(); - - boolean classicHITW = false; - boolean classicHITWMusic = false; - - public boolean isClassicHITW() { - return classicHITW; - } - - public boolean isClassicHITWMusic() { - return classicHITWMusic; - } - - @Override - public ConfigCategory getCategory() { - Option classicHITWOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.classicHITW")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.classicHITW.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.classicHITW, () -> classicHITW, value -> this.classicHITW = value) - .build(); - Option classicMusicOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.classicHITWMusic")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.classicHITWMusic.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.classicHITWMusic, () -> classicHITWMusic, value -> this.classicHITWMusic = value) - .build(); - - return ConfigCategory.createBuilder() - .name(Component.literal("Classic HITW")) - .option(classicHITWOption) - .option(classicMusicOption) - .build(); - } -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/CosmeticsOptions.java b/src/main/java/net/asodev/islandutils/options/categories/CosmeticsOptions.java deleted file mode 100644 index bf9747aa..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/CosmeticsOptions.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; -import net.asodev.islandutils.options.saving.Ignore; -import net.minecraft.network.chat.Component; - -public class CosmeticsOptions implements OptionsCategory { - @Ignore - private static final CosmeticsOptions defaults = new CosmeticsOptions(); - - boolean showPlayerPreview = true; - boolean showOnHover = true; - boolean showOnOnlyCosmeticMenus = true; - - public boolean isShowPlayerPreview() { - return showPlayerPreview; - } - - public boolean isShowOnHover() { - return showOnHover; - } - - public boolean isShowOnOnlyCosmeticMenus() { - return showOnOnlyCosmeticMenus; - } - - @Override - public ConfigCategory getCategory() { - Option showPreviewOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showPlayerPreview")) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showPlayerPreview, () -> showPlayerPreview, value -> this.showPlayerPreview = value) - .build(); - Option showHoverOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showOnHover")) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showOnHover, () -> showOnHover, value -> this.showOnHover = value) - .build(); - Option showInOnlyCosmeticMenu = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showOnOnlyCosmeticMenus")) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showOnOnlyCosmeticMenus, () -> showOnOnlyCosmeticMenus, value -> this.showOnOnlyCosmeticMenus = value) - .build(); - return ConfigCategory.createBuilder() - .name(Component.literal("Cosmetics")) - .option(showPreviewOption) - .option(showHoverOption) - .option(showInOnlyCosmeticMenu) - .build(); - } -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/CraftingOptions.java b/src/main/java/net/asodev/islandutils/options/categories/CraftingOptions.java deleted file mode 100644 index f5347ebe..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/CraftingOptions.java +++ /dev/null @@ -1,68 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; -import net.asodev.islandutils.options.saving.Ignore; -import net.minecraft.network.chat.Component; - -public class CraftingOptions implements OptionsCategory { - @Ignore - private static final CraftingOptions defaults = new CraftingOptions(); - - boolean enableCraftingNotifs = true; - boolean toastNotif = true; - boolean chatNotif = true; - boolean notifyServerList = true; - - public boolean isEnableCraftingNotifs() { - return enableCraftingNotifs; - } - - public boolean isToastNotif() { - return toastNotif; - } - - public boolean isChatNotif() { - return chatNotif; - } - - public boolean isNotifyServerList() { - return notifyServerList; - } - - @Override - public ConfigCategory getCategory() { - Option enableOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.enableCraftingNotifs")) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.enableCraftingNotifs, () -> enableCraftingNotifs, value -> this.enableCraftingNotifs = value) - .build(); - Option toastOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.toastNotif")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.toastNotif.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.toastNotif, () -> toastNotif, value -> this.toastNotif = value) - .build(); - Option chatOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.chatNotif")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.chatNotif.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.chatNotif, () -> chatNotif, value -> this.chatNotif = value) - .build(); - Option notifyOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.notifyServerList")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.notifyServerList.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.notifyServerList, () -> notifyServerList, value -> this.notifyServerList = value) - .build(); - return ConfigCategory.createBuilder() - .name(Component.literal("Crafting Notifications")) - .option(enableOption) - .option(toastOption) - .option(chatOption) - .option(notifyOption) - .build(); - } -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/DiscordOptions.java b/src/main/java/net/asodev/islandutils/options/categories/DiscordOptions.java deleted file mode 100644 index 5d86f7b3..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/DiscordOptions.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionGroup; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; -import net.asodev.islandutils.options.saving.Ignore; -import net.minecraft.network.chat.Component; - -public class DiscordOptions implements OptionsCategory { - @Ignore - private static final DiscordOptions defaults = new DiscordOptions(); - - public boolean discordPresence = true; - public boolean showGame = true; - public boolean showGameInfo = true; - public boolean showTimeRemaining = true; - public boolean showTimeElapsed = true; - - @Override - public ConfigCategory getCategory() { - Option discordPresenceOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.discordPresence")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.discordPresence.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.discordPresence, () -> discordPresence, value -> this.discordPresence = value) - .build(); - Option showGameOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showGame")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showGame.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showGame, () -> showGame, value -> this.showGame = value) - .build(); - Option showGameInfoOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showGameInfo")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showGameInfo.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showGameInfo, () -> showGameInfo, value -> this.showGameInfo = value) - .build(); - Option showTimeRemainOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showTimeRemaining")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showTimeRemaining.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showTimeRemaining, () -> showTimeRemaining, value -> this.showTimeRemaining = value) - .build(); - Option showTimeElapsedOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showTimeElapsed")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showTimeElapsed.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showTimeElapsed, () -> showTimeElapsed, value -> this.showTimeElapsed = value) - .build(); - - return ConfigCategory.createBuilder() - .name(Component.literal("Discord Presence")) - .option(discordPresenceOption) - .group(OptionGroup.createBuilder() - .name(Component.literal("Presence Display Options")) - .collapsed(false) - .option(showGameOption) - .option(showGameInfoOption) - .option(showTimeRemainOption) - .option(showTimeElapsedOption) - .build()) - .build(); - } -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/MiscOptions.java b/src/main/java/net/asodev/islandutils/options/categories/MiscOptions.java deleted file mode 100644 index d3f698b9..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/MiscOptions.java +++ /dev/null @@ -1,124 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionGroup; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; -import net.asodev.islandutils.options.saving.Ignore; -import net.asodev.islandutils.util.Utils; -import net.minecraft.network.chat.Component; - -public class MiscOptions implements OptionsCategory { - @Ignore - private static final MiscOptions defaults = new MiscOptions(); - - boolean pauseConfirm = true; - boolean showFriendsInGame = true; - boolean showFriendsInLobby = true; - boolean silverPreview = true; - boolean channelSwitchers = true; - boolean showFishingUpgradeIcon = true; - boolean enableConfigButton = true; - boolean debugMode = false; - - public boolean isPauseConfirm() { - return pauseConfirm; - } - public boolean isShowFriendsInGame() { - return showFriendsInGame; - } - public boolean isShowFriendsInLobby() { - return showFriendsInLobby; - } - - public boolean isSilverPreview() { - return silverPreview; - } - public boolean isEnableConfigButton() { - return enableConfigButton; - } - public boolean showChannelSwitchers() { - return channelSwitchers; - } - public boolean isShowFishingUpgradeIcon() { - return showFishingUpgradeIcon; - } - - public boolean isDebugMode() { - return debugMode && !Utils.isLunarClient(); - } - - @Override - public ConfigCategory getCategory() { - Option pauseOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.pauseConfirm")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.pauseConfirm.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.pauseConfirm, () -> pauseConfirm, value -> this.pauseConfirm = value) - .build(); - Option showFriendsOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showFriendsInGame")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showFriendsInGame.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showFriendsInGame, () -> showFriendsInGame, value -> this.showFriendsInGame = value) - .build(); - Option showFriendsLobbyOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showFriendsInLobby")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showFriendsInLobby.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showFriendsInLobby, () -> showFriendsInLobby, value -> this.showFriendsInLobby = value) - .build(); - Option silverOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.silverPreview")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.silverPreview.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.silverPreview, () -> silverPreview, value -> this.silverPreview = value) - .build(); - Option channelsOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.channelSwitchers")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.channelSwitchers.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.channelSwitchers, () -> channelSwitchers, value -> this.channelSwitchers = value) - .build(); - Option fishingUpgradeOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showFishingUpgradeIcon")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showFishingUpgradeIcon.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showFishingUpgradeIcon, () -> showFishingUpgradeIcon, value -> this.showFishingUpgradeIcon = value) - .build(); - Option buttonOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.enableConfigButton")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.enableConfigButton.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.enableConfigButton, () -> enableConfigButton, value -> this.enableConfigButton = value) - .build(); - Option debugOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.debugMode")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.debugMode.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .available(!Utils.isLunarClient()) // disable on lunar client - .binding(defaults.debugMode, () -> debugMode, value -> this.debugMode = value) - .build(); - - return ConfigCategory.createBuilder() - .name(Component.literal("Miscellaneous")) - .option(pauseOption) - .group(OptionGroup.createBuilder() - .name(Component.literal("Friends Notifier")) - .collapsed(false) - .option(showFriendsOption) - .option(showFriendsLobbyOption) - .build()) - .option(silverOption) - .option(channelsOption) - .option(fishingUpgradeOption) - .option(buttonOption) - .group(OptionGroup.createBuilder() - .name(Component.literal("Debug Options")) - .collapsed(true) - .option(debugOption) - .build()) - .build(); - } -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/MusicOptions.java b/src/main/java/net/asodev/islandutils/options/categories/MusicOptions.java deleted file mode 100644 index 4829b1e2..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/MusicOptions.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionGroup; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; -import net.asodev.islandutils.modules.music.MusicManager; -import net.asodev.islandutils.modules.music.MusicModifier; -import net.asodev.islandutils.options.saving.Ignore; -import net.minecraft.network.chat.Component; - -import java.util.ArrayList; -import java.util.List; - -public class MusicOptions implements OptionsCategory { - @Ignore - private static final MusicOptions defaults = new MusicOptions(); - - - @Override - public ConfigCategory getCategory() { - List> modifierOptions = new ArrayList<>(); - for (MusicModifier modifier : MusicManager.getModifiers()) { - if (!modifier.hasOption()) continue; - - var option = Option.createBuilder() - .name(modifier.name()) - .description(OptionDescription.of(modifier.desc())) - .controller(TickBoxControllerBuilder::create) - .binding(modifier.defaultOption(), modifier::isEnabled, modifier::setEnabled) - .build(); - modifierOptions.add(option); - } - - return ConfigCategory.createBuilder() - .name(Component.literal("Music")) - .group(OptionGroup.createBuilder() - .name(Component.literal("Modifiers")) - .options(modifierOptions) - .build()) - .build(); - } - -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/OptionsCategory.java b/src/main/java/net/asodev/islandutils/options/categories/OptionsCategory.java deleted file mode 100644 index 57fbed43..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/OptionsCategory.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; - -public interface OptionsCategory { - ConfigCategory getCategory(); -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/PlobbyOptions.java b/src/main/java/net/asodev/islandutils/options/categories/PlobbyOptions.java deleted file mode 100644 index 315959d0..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/PlobbyOptions.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ConfigCategory; -import net.asodev.islandutils.options.saving.Ignore; -import net.minecraft.network.chat.Component; - -public class PlobbyOptions implements OptionsCategory { - @Ignore - private static final PlobbyOptions defaults = new PlobbyOptions(); - - @Override - public ConfigCategory getCategory() { - - - - return ConfigCategory.createBuilder() - .name(Component.literal("Plobby Integration")) - .build(); - } -} diff --git a/src/main/java/net/asodev/islandutils/options/categories/SplitsCategory.java b/src/main/java/net/asodev/islandutils/options/categories/SplitsCategory.java deleted file mode 100644 index 509361bb..00000000 --- a/src/main/java/net/asodev/islandutils/options/categories/SplitsCategory.java +++ /dev/null @@ -1,112 +0,0 @@ -package net.asodev.islandutils.options.categories; - -import dev.isxander.yacl3.api.ButtonOption; -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.controller.EnumControllerBuilder; -import dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder; -import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; -import net.asodev.islandutils.modules.splits.SplitManager; -import net.asodev.islandutils.modules.splits.SplitType; -import net.asodev.islandutils.options.saving.Ignore; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.ConfirmScreen; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; - -public class SplitsCategory implements OptionsCategory { - @Ignore - private static final SplitsCategory defaults = new SplitsCategory(); - - boolean enablePkwSplits = true; - boolean sendSplitTime = true; - boolean showTimer = true; - boolean showSplitImprovements = true; - int showTimerImprovementAt = -3; - SplitType saveMode = SplitType.BEST; - - @Override - public ConfigCategory getCategory() { - Option enableOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.enablePkwSplits")) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.enablePkwSplits, () -> enablePkwSplits, value -> this.enablePkwSplits = value) - .build(); - Option sendOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.sendSplitTime")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.sendSplitTime.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.sendSplitTime, () -> sendSplitTime, value -> this.sendSplitTime = value) - .build(); - Option showOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showTimer")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showTimer.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showTimer, () -> showTimer, value -> this.showTimer = value) - .build(); - Option showImprovesOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showSplitImprovements")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showSplitImprovements.@Tooltip"))) - .controller(TickBoxControllerBuilder::create) - .binding(defaults.showSplitImprovements, () -> showSplitImprovements, value -> this.showSplitImprovements = value) - .build(); - Option showImprovesAtOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.showTimerImprovementAt")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.showTimerImprovementAt.@Tooltip"))) - .controller(IntegerFieldControllerBuilder::create) - .binding(defaults.showTimerImprovementAt, () -> showTimerImprovementAt, value -> this.showTimerImprovementAt = value) - .build(); - Option saveOption = Option.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.saveMode")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.saveMode.@Tooltip"))) - .controller((opt) -> EnumControllerBuilder.create(opt).enumClass(SplitType.class)) - .binding(defaults.saveMode, () -> saveMode, value -> this.saveMode = value) - .build(); - - Option clearSplits = ButtonOption.createBuilder() - .name(Component.translatable("text.autoconfig.islandutils.option.clearSplits")) - .description(OptionDescription.of(Component.translatable("text.autoconfig.islandutils.option.clearSplits.@Tooltip"))) - .action((screen, b) -> doClearSplits(screen)) - .build(); - return ConfigCategory.createBuilder() - .name(Component.literal("Parkour Warrior Splits")) - .option(enableOption) - .option(sendOption) - .option(showOption) - .option(showImprovesOption) - .option(showImprovesAtOption) - .option(saveOption) - .option(clearSplits) - .build(); - } - - private void doClearSplits(Screen parent) { - ConfirmScreen confirmScreen = new ConfirmScreen((bl) -> { - if (bl) SplitManager.clearSplits(); - Minecraft.getInstance().setScreen(parent); - }, Component.literal("Are you sure you want to clear your splits?").withStyle(ChatFormatting.RED), - Component.literal("This action is irreversible").withStyle(ChatFormatting.DARK_RED)); - Minecraft.getInstance().setScreen(confirmScreen); - } - - public boolean isEnablePkwSplits() { - return enablePkwSplits; - } - public boolean isSendSplitTime() { - return enablePkwSplits && sendSplitTime; - } - public boolean isShowTimer() { - return enablePkwSplits && showTimer; - } - public boolean isShowSplitImprovements() { - return enablePkwSplits && showSplitImprovements; - } - public int getShowTimerImprovementAt() { - return showTimerImprovementAt; - } - public SplitType getSaveMode() { - return saveMode; - } -} diff --git a/src/main/java/net/asodev/islandutils/options/saving/Ignore.java b/src/main/java/net/asodev/islandutils/options/saving/Ignore.java deleted file mode 100644 index 384225ba..00000000 --- a/src/main/java/net/asodev/islandutils/options/saving/Ignore.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.asodev.islandutils.options.saving; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -public @interface Ignore { -} diff --git a/src/main/java/net/asodev/islandutils/options/saving/IslandUtilsSaveHandler.java b/src/main/java/net/asodev/islandutils/options/saving/IslandUtilsSaveHandler.java deleted file mode 100644 index d7f8dad0..00000000 --- a/src/main/java/net/asodev/islandutils/options/saving/IslandUtilsSaveHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.asodev.islandutils.options.saving; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.options.categories.OptionsCategory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Field; - -public class IslandUtilsSaveHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(IslandOptions.class); - private Gson gson = new Gson(); - - public void save(OptionsCategory category, JsonObject object) throws IllegalAccessException { - for (Field declaredField : category.getClass().getDeclaredFields()) { - if (declaredField.isAnnotationPresent(Ignore.class)) continue; - declaredField.trySetAccessible(); - - String name = declaredField.getName(); - Object field = declaredField.get(category); - object.add(name, gson.toJsonTree(field)); - } - } - - public void load(OptionsCategory category, JsonObject object) { - for (Field field : category.getClass().getDeclaredFields()) { - if (field.isAnnotationPresent(Ignore.class)) continue; - String name = field.getName(); - JsonElement jsonElement = object.get(name); - if (jsonElement == null) continue; - - field.trySetAccessible(); - try { - Class type = field.getType(); - field.set(category, gson.fromJson(jsonElement, type)); - } catch (Exception e) { - LOGGER.warn("Failed to load config option: " + name, e); - } - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/state/Game.java b/src/main/java/net/asodev/islandutils/state/Game.java deleted file mode 100644 index 74ee8585..00000000 --- a/src/main/java/net/asodev/islandutils/state/Game.java +++ /dev/null @@ -1,76 +0,0 @@ -package net.asodev.islandutils.state; - -import com.noxcrew.noxesium.network.clientbound.ClientboundMccServerPacket; -import net.asodev.islandutils.discord.FishingPresenceUpdator; -import net.minecraft.resources.ResourceLocation; - -import java.util.NoSuchElementException; - -public enum Game { - - HUB("Hub", "", null), - FISHING("Hub", "", null), - - TGTTOS("TGTTOS", "tgttos", getMusicLocation("tgttos")), - HITW("Hole in the Wall", "hole_in_the_wall", getMusicLocation("hitw")), - BATTLE_BOX("Battle Box", "battle_box", getMusicLocation("battle_box"), true), - PARKOUR_WARRIOR_SURVIVOR("Parkour Warrior Survivor", "parkour_warrior", "survival", getMusicLocation("parkour_warrior")), - PARKOUR_WARRIOR_DOJO("Parkour Warrior Dojo", "parkour_warrior", getMusicLocation("parkour_warrior")), - DYNABALL("Dynaball", "dynaball", getMusicLocation("dynaball"), true), - ROCKET_SPLEEF_RUSH("Rocket Spleef Rush", "rocket_spleef", getMusicLocation("rsr")), - SKY_BATTLE("Sky Battle", "sky_battle", getMusicLocation("sky_battle"), true); - - final private String name; - final private String islandId; - final private String subType; - final private ResourceLocation musicLocation; - private boolean hasTeamChat = false; - Game(String name, String islandId, ResourceLocation location) { - this.name = name; - this.islandId = islandId; - this.subType = null; - this.musicLocation = location; - } - Game(String name, String islandId, ResourceLocation location, boolean hasTeamChat) { - this(name, islandId, location); - this.hasTeamChat = hasTeamChat; - } - Game(String name, String islandId, String subType, ResourceLocation location) { - this.name = name; - this.islandId = islandId; - this.subType = subType; - this.musicLocation = location; - } - - public String getName() { - return name; - } - public ResourceLocation getMusicLocation() { - return musicLocation; - } - public boolean hasTeamChat() { - return hasTeamChat; - } - - public static ResourceLocation getMusicLocation(String name) { - return ResourceLocation.fromNamespaceAndPath("island", "island.music." + name); - } - - public static Game fromPacket(ClientboundMccServerPacket packet) throws NoSuchElementException { - if (packet.serverType().equals("lobby")) { - for (String temperature : FishingPresenceUpdator.temperatures) { - if (packet.subType().startsWith(temperature + "_")) return FISHING; - } - return HUB; - } - - for (Game game : values()) { - if (game.islandId.equals(packet.associatedGame())) { - if (game.subType != null && !game.subType.equals(packet.subType())) - continue; - return game; - } - } - throw new NoSuchElementException("Game could not be found from '" + packet.associatedGame() + "' (" + packet.subType() + ")"); - } -} diff --git a/src/main/java/net/asodev/islandutils/state/MccIslandNotifs.java b/src/main/java/net/asodev/islandutils/state/MccIslandNotifs.java deleted file mode 100644 index 83049a5f..00000000 --- a/src/main/java/net/asodev/islandutils/state/MccIslandNotifs.java +++ /dev/null @@ -1,64 +0,0 @@ -package net.asodev.islandutils.state; - -import com.mojang.realmsclient.Unit; -import net.asodev.islandutils.IslandUtils; -import net.asodev.islandutils.modules.crafting.state.CraftingItem; -import net.asodev.islandutils.modules.crafting.state.CraftingItems; -import net.asodev.islandutils.options.IslandOptions; -import net.asodev.islandutils.options.categories.CraftingOptions; -import net.asodev.islandutils.util.resourcepack.ResourcePackUpdater; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; - -import java.util.ArrayList; -import java.util.List; -import java.util.OptionalLong; - -public class MccIslandNotifs { - private static Component completedCrafts = Component.literal("Completed Crafts:").setStyle(Style.EMPTY.withBold(true).withColor(ChatFormatting.WHITE)); - - public static List getNotifLines() { - List components = new ArrayList<>(); - - MccIslandNotifs.addCraftingNotifs(components); - MccIslandNotifs.addPackDownloadNotifs(components); - - return components; - } - - private static void addCraftingNotifs(List components) { - List craftingLists = new ArrayList<>(); - boolean anycomplete = false; - CraftingOptions options = IslandOptions.getCrafting(); - if (options.isEnableCraftingNotifs() && options.isNotifyServerList()) { // "i'm a never-nester" - for (CraftingItem item : CraftingItems.getItems()) { - if (!item.isComplete()) continue; - craftingLists.add(Component.literal(" ").append(item.getTitle())); - anycomplete = true; - } - } - if (!anycomplete) return; - - components.add(completedCrafts); - components.addAll(craftingLists); - } - - private static void addPackDownloadNotifs(List components) { - ResourcePackUpdater.PackDownloadListener currentDownload = IslandUtils.packUpdater.currentDownload; - if (currentDownload == null) return; - - MutableComponent downloadTitle = Component.literal("Downloading Music:") - .setStyle(Style.EMPTY.withBold(true)); - - String bytesText = Unit.humanReadable(currentDownload.getBytesDownloaded()); - OptionalLong size = currentDownload.getSize(); - String maxSize = size.isPresent() ? " / " + Unit.humanReadable(size.getAsLong()) : ""; - Component downloadProgress = Component.literal(" " + bytesText + maxSize); - - components.add(downloadTitle); - components.add(downloadProgress); - } - -} diff --git a/src/main/java/net/asodev/islandutils/state/MccIslandState.java b/src/main/java/net/asodev/islandutils/state/MccIslandState.java deleted file mode 100644 index a0bb8c8e..00000000 --- a/src/main/java/net/asodev/islandutils/state/MccIslandState.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.asodev.islandutils.state; - -import net.asodev.islandutils.IslandUtilsEvents; -import net.asodev.islandutils.util.ChatUtils; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ServerData; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TextColor; - -public class MccIslandState { - - private static Game game = Game.HUB; - private static String modifier = "INACTIVE"; - private static String map = "UNKNOWN"; - private static String subType = ""; - - public static String getModifier() { - return modifier; - } - public static void setModifier(String modifier) { - MccIslandState.modifier = modifier; - } - - public static Game getGame() { - return game; - } - public static void setGame(Game game) { - if (MccIslandState.game != game) { - ChatUtils.debug("MccIslandState - Changed game to: " + game); - IslandUtilsEvents.GAME_CHANGE.invoker().onGameChange(game); - } - MccIslandState.game = game; - IslandUtilsEvents.GAME_UPDATE.invoker().onGameUpdate(game); - } - - public static void updateGame(Component displayName, String tablistTitle) { - String title = displayName.getString(); - - // Check for PKW Tablist Titles - if (tablistTitle.contains("PARKOUR WARRIOR SURVIVOR")) { - MccIslandState.setGame(Game.PARKOUR_WARRIOR_SURVIVOR); - return; - } else if (tablistTitle.contains("Parkour Warrior - ")) { - MccIslandState.setGame(Game.PARKOUR_WARRIOR_DOJO); - return; - } - - if (!isGameDisplayName(displayName)) { - MccIslandState.setGame(Game.HUB); - } else { // We're in a game!!! - // These checks are pretty self-explanatory - if (title.contains("HOLE IN THE WALL")) { - MccIslandState.setGame(Game.HITW); - } else if (title.contains("TGTTOS")) { - MccIslandState.setGame(Game.TGTTOS); - } else if (title.contains("SKY BATTLE")) { - MccIslandState.setGame(Game.SKY_BATTLE); - } else if (title.contains("BATTLE BOX")) { - MccIslandState.setGame(Game.BATTLE_BOX); - } else { - MccIslandState.setGame(Game.HUB); // Somehow we're in a game, but not soooo hub it is!! - } - } - } - static TextColor aqua = TextColor.fromLegacyFormat(ChatFormatting.AQUA); - private static boolean isGameDisplayName(Component component) { - for (Component sibling : component.getSiblings()) { // Get all the elements of this component - if (sibling.getStyle().getColor() == aqua) return true; // If it's aqua, YES - } - return false; // If not... no :( - } - - public static void setMap(String map) { - MccIslandState.map = map; - } - public static String getMap() { - return map; - } - - public static String getSubType() { - return subType; - } - public static void setSubType(String subType) { - MccIslandState.subType = subType; - } - - public static boolean isOnline() { - ServerData currentServer = Minecraft.getInstance().getCurrentServer(); - if (currentServer == null) return false; - String ip = currentServer.ip.toLowerCase(); - return ip.contains("mccisland.net") || ip.contains("mccisland.com"); - } -} diff --git a/src/main/java/net/asodev/islandutils/util/ChatUtils.java b/src/main/java/net/asodev/islandutils/util/ChatUtils.java deleted file mode 100644 index f5d940ef..00000000 --- a/src/main/java/net/asodev/islandutils/util/ChatUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.asodev.islandutils.util; - -import net.asodev.islandutils.options.IslandOptions; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.network.chat.TextColor; -import net.minecraft.resources.ResourceLocation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Optional; - -public class ChatUtils { - private static final Logger LOGGER = LoggerFactory.getLogger("IslandUtils"); - public static final Style iconsFontStyle = Style.EMPTY.withColor(ChatFormatting.WHITE).withFont(ResourceLocation.fromNamespaceAndPath("island","icons")); - public static final String prefix = "&b[&eIslandUtils&b]"; - public static String translate(String s) { - return s.replaceAll("&", "§"); - } - - public static void send(String s) { - send(Component.literal(translate(prefix + " " + s))); - } - - public static void debug(String s, Object... args) { - debug(String.format(s, args)); - } - - public static void debug(String s) { - if (!IslandOptions.getMisc().isDebugMode()) { - LOGGER.info("[DEBUG] {}", s); - return; - } - send(Component.literal("[IslandUtils] " + s).withStyle(ChatFormatting.GRAY)); - } - - public static void send(Component component) { - Minecraft.getInstance().getChatListener().handleSystemMessage(component, false); - } - - public static TextColor parseColor(String hex) { - Optional result = TextColor.parseColor(hex).result(); - return result.orElse(null); - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/IslandSoundEvents.java b/src/main/java/net/asodev/islandutils/util/IslandSoundEvents.java deleted file mode 100644 index dc1ae970..00000000 --- a/src/main/java/net/asodev/islandutils/util/IslandSoundEvents.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.asodev.islandutils.util; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.SoundEvent; - -public class IslandSoundEvents { - public static final SoundEvent UI_CLICK_NORMAL = soundEvent("ui.click_normal"); - public static final SoundEvent UI_ACHIEVEMENT_RECEIVE = soundEvent("ui.achievement_receive"); - public static final SoundEvent ANNOUNCER_GAME_OVER = islandUtilsEvent("announcer.gameover"); - - private static SoundEvent soundEvent(String path) { - return SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath("mcc", path)); - } - - private static SoundEvent islandUtilsEvent(String path) { - return SoundEvent.createVariableRangeEvent(islandSound(path)); - } - - public static ResourceLocation islandSound(String path) { - return ResourceLocation.fromNamespaceAndPath("island", path); - } -} diff --git a/src/main/java/net/asodev/islandutils/util/IslandUtilsCommand.java b/src/main/java/net/asodev/islandutils/util/IslandUtilsCommand.java deleted file mode 100644 index af18809f..00000000 --- a/src/main/java/net/asodev/islandutils/util/IslandUtilsCommand.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.asodev.islandutils.util; - -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import net.asodev.islandutils.IslandUtils; -import net.asodev.islandutils.modules.crafting.state.CraftingNotifier; -import net.asodev.islandutils.util.debug.GameOverride; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.network.chat.Component; - -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; - -public class IslandUtilsCommand { - public static final Component cantUseDebugError = - Component.literal(ChatUtils.translate("You must be in debug mode to use the debug command.")); - - public static LiteralArgumentBuilder craftsCommand = literal("crafting") - .executes(ctx -> { - ctx.getSource().sendFeedback(CraftingNotifier.activeCraftsMessage()); - return 0; - }); - public static LiteralArgumentBuilder debugSubcommand = literal("debug") - .then(CraftingNotifier.getDebugCommand()) - .then(GameOverride.getDebugCommand()); - public static LiteralArgumentBuilder islandCommand = literal("islandutils") - .then(craftsCommand); - - public static void register() { - if (IslandUtils.isPreRelease()) { - islandCommand = islandCommand.then(debugSubcommand); - } - - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> { - dispatcher.register(islandCommand); - dispatcher.register(craftsCommand); - }); - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/MCCSoundInstance.java b/src/main/java/net/asodev/islandutils/util/MCCSoundInstance.java deleted file mode 100644 index c1521fac..00000000 --- a/src/main/java/net/asodev/islandutils/util/MCCSoundInstance.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.asodev.islandutils.util; - -import net.asodev.islandutils.modules.music.SoundInfo; -import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance; -import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.RandomSource; - -import java.util.List; - -public class MCCSoundInstance extends AbstractTickableSoundInstance { - - public ResourceLocation location; - - public float totalVolume; - public float totalFadeTicks = 20f; - public float fadeTicks = 0f; - public boolean isFading = false; - - public MCCSoundInstance(ResourceLocation location, SoundSource soundSource, float volume,float pitch, RandomSource randomSource, double x, double y, double z) { - super(SoundEvent.createVariableRangeEvent(location), soundSource, randomSource); - this.location = location; - this.volume = volume; - this.totalVolume = volume; - this.pitch = pitch; - this.x = x; - this.y = y; - this.z = z; - this.looping = false; - this.delay = 0; - this.attenuation = Attenuation.NONE; - this.relative = false; - } - - public MCCSoundInstance(SoundInfo soundInfo) { - this( - soundInfo.path(), - soundInfo.category(), - soundInfo.volume(), - soundInfo.pitch(), - RandomSource.create(soundInfo.seed()), - soundInfo.x(), - soundInfo.y(), - soundInfo.z() - ); - this.looping = soundInfo.looping(); - } - - public void fade(float ticks) { - isFading = true; - totalFadeTicks = ticks; - fadeTicks = totalFadeTicks; - } - public void stopFwd() { - stop(); - } - - @Override - public void tick() { - if (!isFading) return; - if (fadeTicks <= 0) { - this.volume = totalVolume * (fadeTicks/totalFadeTicks); - this.stop(); - } else { - this.volume = totalVolume * (fadeTicks/totalFadeTicks); - fadeTicks -= 1; - } - } - - @Override - public String toString() { - return "MCCSoundInstance{" + - "location=" + location + - '}'; - } -} diff --git a/src/main/java/net/asodev/islandutils/util/PlainTextButtonNoShadow.java b/src/main/java/net/asodev/islandutils/util/PlainTextButtonNoShadow.java deleted file mode 100644 index 76e74d4a..00000000 --- a/src/main/java/net/asodev/islandutils/util/PlainTextButtonNoShadow.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.asodev.islandutils.util; - -import net.minecraft.client.gui.ComponentPath; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.ComponentUtils; -import net.minecraft.network.chat.Style; -import net.minecraft.util.Mth; -import org.jetbrains.annotations.Nullable; - -public class PlainTextButtonNoShadow extends Button { - private final Font font; - private final Component message; - private final Component underlinedMessage; - - public PlainTextButtonNoShadow(int i, int j, int k, int l, Component component, Button.OnPress onPress, Font font) { - super(i, j, k, l, component, onPress, DEFAULT_NARRATION); - this.font = font; - this.message = component; - this.underlinedMessage = ComponentUtils.mergeStyles(component.copy(), Style.EMPTY.withUnderlined(true)); - } - - @Override - public void renderWidget(GuiGraphics guiGraphics, int i, int j, float f) { - Component component = this.isHoveredOrFocused() ? this.underlinedMessage : this.message; - guiGraphics.drawString(this.font, component, getX(), getY(), 0xFFFFFF | Mth.ceil(this.alpha * 255.0f) << 24, false); - } - - @Nullable - @Override - public ComponentPath nextFocusPath(FocusNavigationEvent focusNavigationEvent) { - return null; - } -} diff --git a/src/main/java/net/asodev/islandutils/util/Scheduler.java b/src/main/java/net/asodev/islandutils/util/Scheduler.java deleted file mode 100644 index d7e44171..00000000 --- a/src/main/java/net/asodev/islandutils/util/Scheduler.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.asodev.islandutils.util; - -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.minecraft.client.Minecraft; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -public class Scheduler { - private final List tasks = new ArrayList<>(); - - public Scheduler() { - ClientTickEvents.END_CLIENT_TICK.register((client) -> { - if (tasks.isEmpty()) return; - tasks.removeIf(s -> s.tick(client)); - }); - } - - public static class Task { - private boolean shouldRemove = false; - private int ticks; - private final Consumer callback; - - public Task(int ticks, Consumer callback) { - this.ticks = ticks; - this.callback = callback; - } - - public boolean tick(Minecraft client) { - if (shouldRemove) return true; - - ticks--; - if (ticks > 0) return false; - callback.accept(client); - return true; - } - - public void cancel() { - shouldRemove = true; - } - } - - private static Scheduler instance; - - public static Task schedule(int afterTicks, Consumer callback) { - Task task = new Task(afterTicks, callback); - instance.tasks.add(task); - return task; - } - public static Task nextTick(Consumer callback) { - return schedule(1, callback); - } - public static void create() { - instance = new Scheduler(); - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/Sidebar.java b/src/main/java/net/asodev/islandutils/util/Sidebar.java deleted file mode 100644 index 277d8429..00000000 --- a/src/main/java/net/asodev/islandutils/util/Sidebar.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.asodev.islandutils.util; - -import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; -import net.minecraft.world.scores.DisplaySlot; -import net.minecraft.world.scores.Objective; -import net.minecraft.world.scores.PlayerScoreEntry; -import net.minecraft.world.scores.PlayerTeam; -import net.minecraft.world.scores.Scoreboard; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - - -public class Sidebar { - - @Nullable - public static Component getSidebarName() { - Objective sidebar = getSidebar(); - return sidebar != null ? sidebar.getDisplayName() : null; - } - - public static List getSidebarLines() { - Scoreboard scoreboard = getScoreboard(); - if (scoreboard == null) return Collections.emptyList(); - Objective sidebar = getSidebar(scoreboard); - if (sidebar == null) return Collections.emptyList(); - - return scoreboard.listPlayerScores(sidebar).stream().map(PlayerScoreEntry::display).collect(Collectors.toList()); - } - - public static int findLine(Predicate predicate) { - return findLine(predicate, getSidebarLines()); - } - public static int findLine(Predicate predicate, List lines) { - int i = 0; - for (Component line : lines) { - if (predicate.test(line)) return i; - i++; - } - return -1; - } - - @Nullable - public static Component getLine(List lines, int line) { - if (lines.size() < line) return null; - return lines.get(line); - } - - @Nullable - private static Objective getSidebar() { - Scoreboard scoreboard = getScoreboard(); - return scoreboard != null ? getSidebar(scoreboard) : null; - } - - @Nullable - private static Objective getSidebar(Scoreboard scoreboard) { - return scoreboard.getDisplayObjective(DisplaySlot.SIDEBAR); - } - - @Nullable - private static Scoreboard getScoreboard() { - Minecraft minecraft = Minecraft.getInstance(); - if (minecraft.level == null) return null; - return minecraft.level.getScoreboard(); - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/TimeUtil.java b/src/main/java/net/asodev/islandutils/util/TimeUtil.java deleted file mode 100644 index 5d6e4191..00000000 --- a/src/main/java/net/asodev/islandutils/util/TimeUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.asodev.islandutils.util; - -import java.util.regex.MatchResult; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class TimeUtil { - static Pattern timeRegex = Pattern.compile("(\\d*)([dhms])"); - - public static long getTimeSeconds(String string) { - long timeSeconds = 0; - Matcher matcher = timeRegex.matcher(string); - - while (matcher.find()) { - MatchResult result = matcher.toMatchResult(); - int value; - - try { value = Integer.parseInt(result.group(1)); } - catch (Exception e) { continue; } - - String time = result.group(2); - switch (time) { - case "d" -> timeSeconds += value * 86400L; - case "h" -> timeSeconds += value * 3600L; - case "m" -> timeSeconds += value * 60L; - case "s" -> timeSeconds += value; - } - } - - return timeSeconds; - } -} diff --git a/src/main/java/net/asodev/islandutils/util/Utils.java b/src/main/java/net/asodev/islandutils/util/Utils.java deleted file mode 100644 index 4d5be7ae..00000000 --- a/src/main/java/net/asodev/islandutils/util/Utils.java +++ /dev/null @@ -1,112 +0,0 @@ -package net.asodev.islandutils.util; - -import net.asodev.islandutils.util.resourcepack.ResourcePackOptions; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.core.component.DataComponents; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.component.CustomData; -import net.minecraft.world.item.component.CustomModelData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class Utils { - private static final Logger logger = LoggerFactory.getLogger(Utils.class); - public static final ExecutorService savingQueue = Executors.newFixedThreadPool(2); - public static final Style MCC_HUD_FONT = Style.EMPTY.withFont(ResourceLocation.fromNamespaceAndPath("mcc", "hud")); - public static final String BLANK_ITEM_ID = "island_interface.generic.blank"; - private static final List NON_PROD_IP_HASHES = List.of( - "e927084bb931f83eece6780afd9046f121a798bf3ff3c78a9399b08c1dfb1aec", // bigrat.mccisland.net easteregg/test ip - "0c932ffaa687c756c4616a24eb49389213519ea8d18e0d9bdfd2d335771c35c7", - "7f0d15bbb2ffaee1bbf0d23e5746afb753333d590f71ff8a5a186d86c3e79dda", - "09445264a9c515c83fc5a0159bda82e25d70d499f80df4a2d1c2f7e2ae6af997" - ); - - public static List getLores(ItemStack item) { - LocalPlayer player = Minecraft.getInstance().player; - if (player == null) return null; - return item.getTooltipLines(Item.TooltipContext.EMPTY, player, TooltipFlag.Default.NORMAL); - } - - public static String readFile(File file) throws Exception { - if (!file.exists()) return null; - - FileInputStream in = new FileInputStream(file); - String json = new String(in.readAllBytes()); - in.close(); - return json; - } - - public static void writeFile(File file, String data) throws IOException { - if (!file.exists()) file.createNewFile(); - - FileOutputStream out = new FileOutputStream(file); - out.write(data.getBytes()); - out.close(); - } - - public static void assertIslandFolder() { - File folder = ResourcePackOptions.islandFolder.toFile(); - if (!folder.exists()) folder.mkdir(); - } - - public static float customModelData(ItemStack item) { - CustomModelData customModelData = item.get(DataComponents.CUSTOM_MODEL_DATA); - return customModelData == null ? 0f : customModelData.floats().getFirst(); - } - - public static ResourceLocation getCustomItemID(ItemStack item) { - CustomData customDataComponent = item.get(DataComponents.CUSTOM_DATA); - if (customDataComponent == null) return null; - - CompoundTag tag = customDataComponent.getUnsafe(); - CompoundTag publicBukkitValues = tag.getCompound("PublicBukkitValues").orElse(null); - if (publicBukkitValues == null) return null; - - String customItemId = publicBukkitValues.getString("mcc:custom_item_id").orElse(null); - if (customItemId == null || customItemId.isEmpty()) return null; - - return ResourceLocation.parse(customItemId); - } - - public static boolean isProdMCCI(String hostname) { - String hostnameHash = Utils.calculateSha256(hostname); - return !NON_PROD_IP_HASHES.contains(hostnameHash); - } - - public static String calculateSha256(String input) { - String output = ""; - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(input.getBytes(StandardCharsets.UTF_8)); - byte[] digest = md.digest(); - output = String.format("%064x", new BigInteger(1, digest)); - } catch (NoSuchAlgorithmException e) { - logger.error("Failed to calculate SHA-256 for " + input); - } - return output; - } - - public static boolean isLunarClient() { - return FabricLoader.getInstance().isModLoaded("ichor"); - } -} diff --git a/src/main/java/net/asodev/islandutils/util/debug/GameOverride.java b/src/main/java/net/asodev/islandutils/util/debug/GameOverride.java deleted file mode 100644 index ac21a26d..00000000 --- a/src/main/java/net/asodev/islandutils/util/debug/GameOverride.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.asodev.islandutils.util.debug; - -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import net.asodev.islandutils.state.Game; -import net.asodev.islandutils.state.MccIslandState; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; - -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; - -public class GameOverride { - - public static LiteralArgumentBuilder getDebugCommand() { - return literal("override_game") - .then(argument("game", StringArgumentType.greedyString()) - .executes(ctx -> { - String game = ctx.getArgument("game", String.class); - Game gameGame = Game.valueOf(game.toUpperCase()); - MccIslandState.setGame(gameGame); - return 1; - })); - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/resourcepack/IslandUtilsRepositorySource.java b/src/main/java/net/asodev/islandutils/util/resourcepack/IslandUtilsRepositorySource.java deleted file mode 100644 index d2fbc1b8..00000000 --- a/src/main/java/net/asodev/islandutils/util/resourcepack/IslandUtilsRepositorySource.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.asodev.islandutils.util.resourcepack; - -import net.minecraft.server.packs.repository.Pack; -import net.minecraft.server.packs.repository.RepositorySource; - -import java.util.function.Consumer; - -public class IslandUtilsRepositorySource implements RepositorySource { - @Override - public void loadPacks(Consumer consumer) { - if (ResourcePackUpdater.pack != null) { - consumer.accept(ResourcePackUpdater.pack); - } - } -} diff --git a/src/main/java/net/asodev/islandutils/util/resourcepack/ResourcePackOptions.java b/src/main/java/net/asodev/islandutils/util/resourcepack/ResourcePackOptions.java deleted file mode 100644 index e1ddafde..00000000 --- a/src/main/java/net/asodev/islandutils/util/resourcepack/ResourcePackOptions.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.asodev.islandutils.util.resourcepack; - -import net.asodev.islandutils.util.Utils; -import net.asodev.islandutils.util.resourcepack.schema.ResourcePack; -import net.fabricmc.loader.api.FabricLoader; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; - -public class ResourcePackOptions { - - public static final Path islandFolder = FabricLoader.getInstance().getConfigDir().resolve("islandutils_resources"); - public static final Path packDataFile = islandFolder.resolve("pack.json"); - public static final Path packZip = islandFolder.resolve("island_utils.zip"); - - public static ResourcePack data; - - public static void save() throws IOException { - Utils.assertIslandFolder(); - Utils.writeFile(packDataFile.toFile(), data.toJson()); - } - - public static ResourcePack get() throws Exception { - File packData = packDataFile.toFile(); - String json = Utils.readFile(packData); - if (json == null) return null; - - data = ResourcePack.fromJson(json); - return data; - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/resourcepack/ResourcePackUpdater.java b/src/main/java/net/asodev/islandutils/util/resourcepack/ResourcePackUpdater.java deleted file mode 100644 index 0aee88b8..00000000 --- a/src/main/java/net/asodev/islandutils/util/resourcepack/ResourcePackUpdater.java +++ /dev/null @@ -1,183 +0,0 @@ -package net.asodev.islandutils.util.resourcepack; - -import com.google.gson.Gson; -import net.asodev.islandutils.util.resourcepack.schema.ResourcePack; -import net.minecraft.FileUtil; -import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; -import net.minecraft.server.packs.FilePackResources; -import net.minecraft.server.packs.PackLocationInfo; -import net.minecraft.server.packs.PackSelectionConfig; -import net.minecraft.server.packs.repository.Pack; -import net.minecraft.server.packs.repository.PackCompatibility; -import net.minecraft.server.packs.repository.PackSource; -import net.minecraft.util.HttpUtil; -import net.minecraft.world.flag.FeatureFlagSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.concurrent.CompletableFuture; - -import static net.asodev.islandutils.util.ChatUtils.translate; - -public class ResourcePackUpdater { - public static final Logger logger = LoggerFactory.getLogger(ResourcePackUpdater.class); - - private static final String url = "https://raw.githubusercontent.com/AsoDesu/islandutils-assets/master/pack.json"; - private static final Component title = Component.literal(translate("Island Utils")); - private static final Component desc = Component.literal(translate("&6Music Resources")); - HttpClient client; - Gson gson; - - public PackDownloadListener currentDownload = null; - public boolean getting = false; - public boolean accepted = false; - public static Pack pack; - - public ResourcePackUpdater() { - client = HttpClient.newBuilder().build(); - gson = new Gson(); - } - - public CompletableFuture downloadAndApply() { - Minecraft minecraft = Minecraft.getInstance(); - logger.info("Downloading resource pack..."); - - return CompletableFuture.runAsync(() -> { - this.currentDownload = new PackDownloadListener(); - - Path outputFile = ResourcePackOptions.packZip; - - try { - FileUtil.createDirectoriesSafe(outputFile.getParent()); - - URL url = new URL(ResourcePackOptions.data.url); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(minecraft.getProxy()); - urlConnection.setInstanceFollowRedirects(true); - InputStream inputStream = urlConnection.getInputStream(); - - try (OutputStream outputStream = Files.newOutputStream(outputFile, StandardOpenOption.CREATE)){ - int j; - byte[] bs = new byte[8196]; - long l = 0L; - while ((j = inputStream.read(bs)) >= 0) { - this.currentDownload.downloadedBytes(l); - outputStream.write(bs, 0, j); - } - } - - logger.info("Applying resource pack..."); - apply(outputFile.toFile(), true); - } catch (Exception e) { - this.currentDownload = null; - logger.error("Failed to download resource pack. ", e); - } - }); - } - - public void apply(File file, Boolean save) { - getting = false; - currentDownload = null; - pack = new Pack( - new PackLocationInfo("island_utils", title, PackSource.BUILT_IN, Optional.empty()), - new FilePackResources.FileResourcesSupplier(file), - new Pack.Metadata(desc, PackCompatibility.COMPATIBLE, FeatureFlagSet.of(), List.of()), - new PackSelectionConfig(true, Pack.Position.BOTTOM, true) - ); - if (save) { - try { ResourcePackOptions.save(); } - catch (IOException e) { System.err.println("Failed to save resource pack options"); } - } - } - - public CompletableFuture get() { - return CompletableFuture.runAsync(this::doGet); - } - - private void doGet() { - File file = ResourcePackOptions.packZip.toFile(); - if (file.exists()) apply(file, false); - - logger.info("Requesting resource pack..."); - try { - ResourcePack current = ResourcePackOptions.get(); - logger.info("Current resource pack version: " + current); - - ResourcePack rp = requestUpdate(); - logger.info("Received Resource Pack: " + rp.rev); - if (current != null && Objects.equals(current.rev, rp.rev) && file.exists()) { - logger.info("Resource pack has not changed. Not downloading!"); - apply(file, false); - return; - } - ResourcePackOptions.data = rp; - - CompletableFuture download = downloadAndApply(); - download.thenAccept((v) -> { - getting = false; - }); - } catch (Exception e) { - getting = false; - logger.error("Failed to get IslandUtils resource pack info!", e); - } - } - - private ResourcePack requestUpdate() throws Exception { - HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build(); - logger.info("Requesting resource pack: " + req.uri()); - HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString()); - if (res.statusCode() != 200) { - throw new RuntimeException("Got " + res.statusCode() + "code from github. Response:" + res.body()); - } - return gson.fromJson(res.body(), ResourcePack.class); - } - - public static class PackDownloadListener implements HttpUtil.DownloadProgressListener { - private OptionalLong size = OptionalLong.empty(); - private long bytesDownloaded = 0; - - @Override - public void downloadStart(OptionalLong optionalLong) { - logger.info("Downloading IslandUtils Resources... Size: " + optionalLong); - size = optionalLong; - } - - @Override - public void downloadedBytes(long l) { - bytesDownloaded = l; - } - - public OptionalLong getSize() { - return size; - } - public long getBytesDownloaded() { - return bytesDownloaded; - } - - // don't care crown - @Override - public void requestFinished(boolean bl) { - } - @Override - public void requestStart() { - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/resourcepack/schema/ResourcePack.java b/src/main/java/net/asodev/islandutils/util/resourcepack/schema/ResourcePack.java deleted file mode 100644 index 4813bc20..00000000 --- a/src/main/java/net/asodev/islandutils/util/resourcepack/schema/ResourcePack.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.asodev.islandutils.util.resourcepack.schema; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import net.asodev.islandutils.util.resourcepack.ResourcePackUpdater; - -public class ResourcePack { - - @SerializedName("url") - @Expose - public String url; - - @SerializedName("rev") - @Expose - public Integer rev; - - public String toJson() { - return (new Gson()).toJson(this); - } - - public static ResourcePack fromJson(String res) { - JsonObject object = new Gson().fromJson(res, JsonObject.class); - if (object.has("hash")) { - ResourcePackUpdater.logger.info("Ignoring current pack file (Using the old format)"); - return null; - } - - return (new Gson()).fromJson(object, ResourcePack.class); - } - - @Override - public String toString() { - return "ResourcePack{" + - "url='" + url + '\'' + - ", rev=" + rev + - '}'; - } -} diff --git a/src/main/java/net/asodev/islandutils/util/updater/UpdateManager.java b/src/main/java/net/asodev/islandutils/util/updater/UpdateManager.java deleted file mode 100644 index 2e17a420..00000000 --- a/src/main/java/net/asodev/islandutils/util/updater/UpdateManager.java +++ /dev/null @@ -1,81 +0,0 @@ -package net.asodev.islandutils.util.updater; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import net.asodev.islandutils.IslandUtils; -import net.asodev.islandutils.util.updater.schema.AvailableUpdate; -import net.asodev.islandutils.util.updater.schema.ModrinthVersion; -import net.fabricmc.loader.api.Version; -import net.fabricmc.loader.api.VersionParsingException; -import net.minecraft.SharedConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; -import java.net.URLEncoder; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -public class UpdateManager { - private static final Logger logger = LoggerFactory.getLogger(UpdateManager.class); - HttpClient client; - Gson gson; - - public UpdateManager() { - client = HttpClient.newBuilder().build(); - gson = new Gson(); - } - - public CompletableFuture> checkForUpdates() throws Exception { - CompletableFuture> f = new CompletableFuture<>(); - String version = "[\"" + SharedConstants.getCurrentVersion().getName() + "\"]"; - String url = String.format( - "https://api.modrinth.com/v2/project/island-utils/version?game_versions=%s&loaders=%s", - URLEncoder.encode(version, StandardCharsets.UTF_8), - URLEncoder.encode("[\"fabric\"]", StandardCharsets.UTF_8) - ); - - URI updatorURI = new URI(url); - HttpRequest req = HttpRequest.newBuilder(updatorURI).GET().build(); - - client.sendAsync(req, HttpResponse.BodyHandlers.ofString()).thenAccept(res -> { - TypeToken type = TypeToken.getParameterized(List.class, ModrinthVersion.class); - List json = (List) gson.fromJson(res.body(), type); - f.complete(json); - }); - - return f; - } - - public void runUpdateCheck() { - try { - checkForUpdates().thenAccept(res -> { - if (res == null) return; - ModrinthVersion newVersion = null; - for (ModrinthVersion version : res) { - try { - Version updateVersion = Version.parse(version.version_number()); - if (IslandUtils.version.compareTo(updateVersion) < 0) { - newVersion = version; - break; - } - } catch (VersionParsingException e) { - logger.error("Unable to parse version: '" + version.version_number() + "'"); - } - } - if (newVersion != null) { - String version = newVersion.version_number(); - IslandUtils.availableUpdate = - new AvailableUpdate(newVersion.name(), version, "https://modrinth.com/mod/island-utils/version/" + version); - } - }); - } catch (Exception e) { - logger.error("Failed to get IslandUtils Update!"); - } - } - -} diff --git a/src/main/java/net/asodev/islandutils/util/updater/schema/AvailableUpdate.java b/src/main/java/net/asodev/islandutils/util/updater/schema/AvailableUpdate.java deleted file mode 100644 index 6e1e0e42..00000000 --- a/src/main/java/net/asodev/islandutils/util/updater/schema/AvailableUpdate.java +++ /dev/null @@ -1,3 +0,0 @@ -package net.asodev.islandutils.util.updater.schema; - -public record AvailableUpdate(String title, String version, String releaseUrl) {} diff --git a/src/main/java/net/asodev/islandutils/util/updater/schema/ModrinthVersion.java b/src/main/java/net/asodev/islandutils/util/updater/schema/ModrinthVersion.java deleted file mode 100644 index b6731f15..00000000 --- a/src/main/java/net/asodev/islandutils/util/updater/schema/ModrinthVersion.java +++ /dev/null @@ -1,3 +0,0 @@ -package net.asodev.islandutils.util.updater.schema; - -public record ModrinthVersion(String name, String version_number) {} diff --git a/src/main/kotlin/dev/asodesu/islandutils/Command.kt b/src/main/kotlin/dev/asodesu/islandutils/Command.kt new file mode 100644 index 00000000..748ef1d4 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/Command.kt @@ -0,0 +1,20 @@ +package dev.asodesu.islandutils + +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import dev.asodesu.islandutils.api.game.gamesDebugCommand +import dev.asodesu.islandutils.features.crafting.craftingCommand +import dev.asodesu.islandutils.features.crafting.craftingDebugCommand +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.fabricmc.loader.api.FabricLoader + +fun islandUtilsCommand(): LiteralArgumentBuilder? { + var command = literal("islandutils") + .then(craftingCommand()) + if (FabricLoader.getInstance().isDevelopmentEnvironment) command = command.then(debugCommand()) + return command +} + +fun debugCommand() = literal("debug") + .then(craftingDebugCommand()) + .then(gamesDebugCommand()) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/Font.kt b/src/main/kotlin/dev/asodesu/islandutils/Font.kt new file mode 100644 index 00000000..aadd82b4 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/Font.kt @@ -0,0 +1,29 @@ +package dev.asodesu.islandutils + +import dev.asodesu.islandutils.api.extentions.Resources +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.FontDescription +import net.minecraft.network.chat.Style + +object Font { + val HUD = Resources.mcc("hud") + val HUD_STYLE = Style.EMPTY.withFont(FontDescription.Resource(HUD)) + + val ICONS = Resources.islandUtils("icons") + val ICONS_STYLE = Style.EMPTY.withFont(FontDescription.Resource(ICONS)).withColor(ChatFormatting.WHITE) + + val SOCIAL_ICON = icon("\ue001") + val FUSION_ICON = icon("\ue002") + val ASSEMBLER_ICON = icon("\ue003") + val ACTION_CLICK_LEFT = icon("\ue004") + val ACTION_CLICK_RIGHT = icon("\ue005") + val HAT_BADGE = icon("\ue006") + val ACCESSORY_BADGE = icon("\ue007") + val CLOAK_BADGE = icon("\ue008") + val ROD_BADGE = icon("\ue009") + + private fun icon(char: String): Component { + return Component.literal(char).withStyle(ICONS_STYLE) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/IslandUtils.kt b/src/main/kotlin/dev/asodesu/islandutils/IslandUtils.kt new file mode 100644 index 00000000..d01bcc33 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/IslandUtils.kt @@ -0,0 +1,41 @@ +package dev.asodesu.islandutils + +import com.noxcrew.noxesium.core.fabric.mcc.MccNoxesiumEntrypoint +import dev.asodesu.islandutils.api.Scheduler +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.islandUtilsFolder +import dev.asodesu.islandutils.api.modules.ModuleManager +import dev.asodesu.islandutils.features.crafting.craftingCommand +import dev.asodesu.islandutils.options.Options +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback +import net.fabricmc.loader.api.FabricLoader +import net.fabricmc.loader.api.ModContainer +import net.minecraft.client.KeyMapping +import org.slf4j.LoggerFactory +import kotlin.io.path.createDirectories +import kotlin.io.path.exists + +object IslandUtils : MccNoxesiumEntrypoint() { + val keyMappingCategory = KeyMapping.Category.register(Resources.islandUtils("islandutils")) + val logger = LoggerFactory.getLogger("IslandUtils") + + val modId = "islandutils" + lateinit var modContainer: ModContainer + lateinit var modVersion: String + + override fun initialize() { + if (!islandUtilsFolder.exists()) islandUtilsFolder.createDirectories() + this.modContainer = FabricLoader.getInstance().getModContainer(modId).get() + this.modVersion = this.modContainer.metadata.version.friendlyString + + Options.load() + + Scheduler.init() + ModuleManager.init() + + ClientCommandRegistrationCallback.EVENT.register { dispatcher, _ -> + dispatcher.register(islandUtilsCommand()) + dispatcher.register(craftingCommand()) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/Modules.kt b/src/main/kotlin/dev/asodesu/islandutils/Modules.kt new file mode 100644 index 00000000..3fa42cba --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/Modules.kt @@ -0,0 +1,97 @@ +package dev.asodesu.islandutils + +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalysisManager +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.game.GameManager +import dev.asodesu.islandutils.api.game.state.StateManager +import dev.asodesu.islandutils.api.music.MusicManager +import dev.asodesu.islandutils.api.music.MusicManager.Track +import dev.asodesu.islandutils.cosmetics.CosmeticUI +import dev.asodesu.islandutils.features.ClassicHitw +import dev.asodesu.islandutils.features.CommandKeybind +import dev.asodesu.islandutils.features.FishingUpgradeHighlight +import dev.asodesu.islandutils.features.FriendsInGame +import dev.asodesu.islandutils.features.RemnantHighlight +import dev.asodesu.islandutils.features.crafting.CraftingChestAnalyser +import dev.asodesu.islandutils.features.crafting.notif.CraftingNotifier +import dev.asodesu.islandutils.features.scavenging.ScavengingTotals +import dev.asodesu.islandutils.games.BattleBox +import dev.asodesu.islandutils.games.Dynaball +import dev.asodesu.islandutils.games.Fishing +import dev.asodesu.islandutils.games.HoleInTheWall +import dev.asodesu.islandutils.games.Hub +import dev.asodesu.islandutils.games.ParkourWarriorDojo +import dev.asodesu.islandutils.games.ParkourWarriorSurvivor +import dev.asodesu.islandutils.games.RocketSpleefRush +import dev.asodesu.islandutils.games.SkyBattle +import dev.asodesu.islandutils.games.Tgttos +import dev.asodesu.islandutils.music.ClassicHitwMusic +import dev.asodesu.islandutils.music.HighQualityMusic +import dev.asodesu.islandutils.music.OldDynaballMusic +import dev.asodesu.islandutils.music.TgttosDomeMusic +import dev.asodesu.islandutils.music.TgttosDoubleTime + +/** + * The modules used which require initialisation. + * Every field in this class of type `Module` will be initialised + * as a module at startup. + */ +object Modules { + // modules which are defined as objects + val objects = listOf( + StateManager, + FriendsInGame, + ClassicHitw, + CraftingNotifier, + CommandKeybind("disguise", -1, "disguise"), + CommandKeybind("plobbymenu", -1, "plobby"), + DiscordManager + ) + + // the game manager, the order here does matter. + val gameManager = GameManager( + Fishing, + BattleBox, + Dynaball, + HoleInTheWall, + ParkourWarriorDojo, + ParkourWarriorSurvivor, + RocketSpleefRush, + SkyBattle, + Tgttos, + Hub + ) + + val musicManager = MusicManager( + knownTracks = listOf( + Track("music.global.parkour_warrior"), + Track("music.global.battle_box"), + Track("music.global.dynaball"), + Track("music.global.hole_in_the_wall"), + Track("music.global.sky_battle"), + Track("music.global.tgttosawaf"), + Track("music.global.overtime_loop_music"), + Track("music.global.overtime_intro_music"), + Track("music.global.gameendmusic", loop = false), + ), + modifiers = listOf( + ClassicHitwMusic, + HighQualityMusic, + OldDynaballMusic, + TgttosDomeMusic, + TgttosDoubleTime + ) + ) + + val chestAnalysis = ChestAnalysisManager( + factories = listOf( + CraftingChestAnalyser.Factory, + ScavengingTotals.Factory, + FishingUpgradeHighlight, + CosmeticUI.Factory + ), + analysers = listOf( + RemnantHighlight + ) + ) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/Sounds.kt b/src/main/kotlin/dev/asodesu/islandutils/Sounds.kt new file mode 100644 index 00000000..e1f6bef4 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/Sounds.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils + +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.toSoundEvent + +object Sounds { + val UI_ACHIVEMENT_RECEIVE = Resources.mcc("ui.achievement_receive").toSoundEvent() +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/Debounce.kt b/src/main/kotlin/dev/asodesu/islandutils/api/Debounce.kt new file mode 100644 index 00000000..c4112ba9 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/Debounce.kt @@ -0,0 +1,16 @@ +package dev.asodesu.islandutils.api + +import kotlin.time.Duration + +class Debounce(cooldown: Duration) { + private val duration = cooldown.inWholeNanoseconds + private var lastTrigger = 0L + + fun consume(): Boolean { + if (lastTrigger() < duration) return false + lastTrigger = System.nanoTime() + return true + } + + fun lastTrigger() = System.nanoTime() - lastTrigger +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/Messages.kt b/src/main/kotlin/dev/asodesu/islandutils/api/Messages.kt new file mode 100644 index 00000000..ef9f3121 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/Messages.kt @@ -0,0 +1,22 @@ +package dev.asodesu.islandutils.api + +import dev.asodesu.islandutils.api.extentions.buildComponent +import dev.asodesu.islandutils.api.extentions.style +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TextColor + +object Messages { + + val ACTION_PREFIX_COLOR = TextColor.parseColor("#505050").orThrow + val ACTION_DESCRIPTION_COLOR = TextColor.parseColor("#ECD584").orThrow + val ACTION_TRIGGER_COLOR = TextColor.parseColor("#FEE761").orThrow + fun action(actionIcons: Component, action: String, trigger: String): Component { + return buildComponent { + append(actionIcons) + append(Component.literal(" > ").style { withColor(ACTION_PREFIX_COLOR) }) + append(Component.literal("$action to ").style { withColor(ACTION_DESCRIPTION_COLOR) }) + append(Component.literal(trigger).style { withColor(ACTION_TRIGGER_COLOR) }) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/Scheduler.kt b/src/main/kotlin/dev/asodesu/islandutils/api/Scheduler.kt new file mode 100644 index 00000000..46561197 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/Scheduler.kt @@ -0,0 +1,52 @@ +package dev.asodesu.islandutils.api + +import dev.asodesu.islandutils.api.extentions.ticks +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.minecraft.client.Minecraft +import kotlin.time.Duration + +/** + * A simple tick scheduler + */ +object Scheduler { + private val tasks = mutableListOf() + fun init() { + ClientTickEvents.END_CLIENT_TICK.register { + this.tick(it) + } + } + + private fun tick(client: Minecraft) { + if (tasks.isEmpty()) return + + tasks.removeIf { it.tick(client) } + } + + /** + * Schedule a new task to execute in the specified duration + * + * @param duration The amount of time to wait + * @param callback The task to execute + */ + fun runAfter(duration: Duration, callback: (Minecraft) -> Unit): Task { + return Task(duration, callback) + .also { tasks += it } + } + + class Task(duration: Duration, private val callback: (Minecraft) -> Unit) { + private var remove = false + private var ticks = duration.ticks + + fun tick(client: Minecraft): Boolean { + if (remove) return true + + if (--ticks > 0) return false + callback(client) + return true + } + + fun cancel() { + remove = true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/ItemExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/ItemExt.kt new file mode 100644 index 00000000..715757cd --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/ItemExt.kt @@ -0,0 +1,25 @@ +package dev.asodesu.islandutils.api.chest + +import net.minecraft.core.component.DataComponents +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.resources.Identifier +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.component.CustomData +import kotlin.jvm.optionals.getOrNull + +val ItemStack.loreOrNull: List? + get() = this.components.get(DataComponents.LORE)?.lines +val ItemStack.lore: List + get() = loreOrNull ?: emptyList() + +fun List.anyLineContains(str: String) = this.any { it.string.contains(str) } + +val ItemStack.customData: CustomData? + get() = this.components.get(DataComponents.CUSTOM_DATA) + +val ItemStack.publicBukkitValues: CompoundTag? + get() = this.customData?.tag?.getCompound("PublicBukkitValues")?.getOrNull() + +val ItemStack.customItemId: Identifier? + get() = this.get(DataComponents.ITEM_MODEL) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalyser.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalyser.kt new file mode 100644 index 00000000..496f9bcd --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalyser.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.api.chest.analysis + +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.ItemStack + +interface ChestAnalyser { + fun analyse(item: ItemStack, slot: Int) {} + fun tick(helper: ContainerScreenHelper) {} + fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, helper: ContainerScreenHelper) {} + fun renderSlotFront(guiGraphics: GuiGraphics, helper: ContainerScreenHelper, slot: Slot) {} + fun renderSlotBack(guiGraphics: GuiGraphics, helper: ContainerScreenHelper, slot: Slot) {} + fun mouseDragged(helper: ContainerScreenHelper, mouseX: Double, mouseY: Double, deltaX: Double, deltaY: Double) {} + fun mouseReleased(helper: ContainerScreenHelper, mouseX: Double, mouseY: Double, keyCode: Int) {} + fun keyPressed(helper: ContainerScreenHelper, keyCode: Int, scanCode: Int, modifiers: Int) {} + fun close(helper: ContainerScreenHelper) {} +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalyserFactory.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalyserFactory.kt new file mode 100644 index 00000000..f8e67062 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalyserFactory.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.chest.analysis + +import net.minecraft.resources.Identifier + +interface ChestAnalyserFactory { + fun shouldApply(menuComponents: Collection): Boolean + fun create(menuComponents: Collection): ChestAnalyser +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalysisManager.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalysisManager.kt new file mode 100644 index 00000000..608dafe5 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ChestAnalysisManager.kt @@ -0,0 +1,39 @@ +package dev.asodesu.islandutils.api.chest.analysis + +import dev.asodesu.islandutils.api.modules.Module +import net.minecraft.resources.Identifier + +class ChestAnalysisManager(factories: List, val analysers: List) : Module("ChestAnalysis") { + private val factories = factories.toList() + + override fun init() { + } + + fun createAnalyser(menuComponents: Collection): ChestAnalyser? { + val factories = factories.filter { + try { + it.shouldApply(menuComponents) + } catch (e: Exception) { + logger.error("Error checking apply on ChestAnalyserFactory $it", e) + false + } + } + + return MultiChestAnalyser(buildList { + factories.forEach { factory -> + val analyser = factory.createCatching(menuComponents) ?: return@forEach + add(analyser) + } + addAll(analysers) + }) + } + + private fun ChestAnalyserFactory.createCatching(menuComponents: Collection): ChestAnalyser? { + return try { + create(menuComponents) + } catch (e: Exception) { + logger.error("Error creating ChestAnalyser $this", e) + return null + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ContainerScreenHelper.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ContainerScreenHelper.kt new file mode 100644 index 00000000..0158362d --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/ContainerScreenHelper.kt @@ -0,0 +1,15 @@ +package dev.asodesu.islandutils.api.chest.analysis + +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.resources.Identifier +import net.minecraft.world.inventory.Slot + +interface ContainerScreenHelper { + fun getMenuComponents(): Collection + fun getAnalyser(): ChestAnalyser? + + val imageWidth: Int + val imageHeight: Int + fun getHoveredSlot(): Slot? + fun getScreen(): AbstractContainerScreen<*> +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/MultiChestAnalyser.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/MultiChestAnalyser.kt new file mode 100644 index 00000000..a811c5ab --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/analysis/MultiChestAnalyser.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.api.chest.analysis + +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.ItemStack + +class MultiChestAnalyser(private val analysers: List) : ChestAnalyser { + override fun analyse(item: ItemStack, slot: Int) = analysers.forEach { it.analyse(item, slot) } + override fun tick(helper: ContainerScreenHelper) = analysers.forEach { it.tick(helper) } + override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, helper: ContainerScreenHelper) = analysers.forEach { it.render(guiGraphics, mouseX, mouseY, helper) } + override fun renderSlotFront(guiGraphics: GuiGraphics, helper: ContainerScreenHelper, slot: Slot) = analysers.forEach { it.renderSlotFront(guiGraphics, helper, slot) } + override fun renderSlotBack(guiGraphics: GuiGraphics, helper: ContainerScreenHelper, slot: Slot) = analysers.forEach { it.renderSlotBack(guiGraphics, helper, slot) } + override fun mouseDragged(helper: ContainerScreenHelper, mouseX: Double, mouseY: Double, deltaX: Double, deltaY: Double) = analysers.forEach { it.mouseDragged(helper, mouseX, mouseY, deltaX, deltaY) } + override fun keyPressed(helper: ContainerScreenHelper, keyCode: Int, scanCode: Int, modifiers: Int) = analysers.forEach { it.keyPressed(helper, keyCode, scanCode, modifiers) } + override fun mouseReleased(helper: ContainerScreenHelper, mouseX: Double, mouseY: Double, keyCode: Int) = analysers.forEach { it.mouseReleased(helper, mouseX, mouseY, keyCode) } + override fun close(helper: ContainerScreenHelper) = analysers.forEach { it.close(helper) } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/ChestBackgrounds.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/ChestBackgrounds.kt new file mode 100644 index 00000000..e5956730 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/ChestBackgrounds.kt @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.api.chest.font + +import dev.asodesu.islandutils.api.extentions.Resources +import java.util.* +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.FontDescription +import net.minecraft.network.chat.Style +import net.minecraft.resources.Identifier + +object ChestBackgrounds : FontCollection.Font { + val FONT_KEY = FontDescription.Resource(Resources.mcc("chest_backgrounds")) + private val CHEST_BACKGROUND_STYLE = Style.EMPTY.withFont(FONT_KEY).withColor(ChatFormatting.WHITE) + private val menuComponents = mutableMapOf() + + override fun add(file: Identifier, character: String) { + val component = Component.literal(character).withStyle(CHEST_BACKGROUND_STYLE) + menuComponents[component] = file + } + + override fun clear() = menuComponents.clear() + + fun get(component: Component): Collection { + val flatComponent = component.toFlatList() + return menuComponents.filter { Collections.indexOfSubList(flatComponent, listOf(it.key)) != -1 }.values + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/FontCollection.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/FontCollection.kt new file mode 100644 index 00000000..cc8bc1b8 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/FontCollection.kt @@ -0,0 +1,26 @@ +package dev.asodesu.islandutils.api.chest.font + +import net.minecraft.client.gui.font.providers.BitmapProvider +import net.minecraft.client.gui.font.providers.GlyphProviderDefinition +import net.minecraft.resources.Identifier + +object FontCollection { + val collections = mutableMapOf( + ChestBackgrounds.FONT_KEY.id to ChestBackgrounds, + Icons.FONT_KEY.id to Icons, + ) + + fun add(font: Identifier, definition: GlyphProviderDefinition) { + val bitmap = definition as? BitmapProvider.Definition ?: return + val collection = collections[font] ?: return + val character = buildString { + bitmap.codepointGrid[0].forEach { this.appendCodePoint(it) } + } + collection.add(bitmap.file, character) + } + + interface Font { + fun add(file: Identifier, character: String) + fun clear() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/Icons.kt b/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/Icons.kt new file mode 100644 index 00000000..3a04cf15 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/chest/font/Icons.kt @@ -0,0 +1,25 @@ +package dev.asodesu.islandutils.api.chest.font + +import dev.asodesu.islandutils.api.extentions.Resources +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.FontDescription +import net.minecraft.network.chat.Style +import net.minecraft.resources.Identifier + +object Icons : FontCollection.Font { + val FONT_KEY = FontDescription.Resource(Resources.mcc("icon")) + val ICON_STYLE = Style.EMPTY.withFont(FONT_KEY).withColor(ChatFormatting.WHITE) + private val characterToFileMap = mutableMapOf() + + override fun add(file: Identifier, character: String) { + characterToFileMap[file] = character + } + + fun getCharacter(file: Identifier) = characterToFileMap[file] + fun toIcon(character: String) = Component.literal(character).withStyle(ICON_STYLE) + + override fun clear() { + characterToFileMap.clear() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/ActivityContainerBuilder.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/ActivityContainerBuilder.kt new file mode 100644 index 00000000..1dff86e2 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/ActivityContainerBuilder.kt @@ -0,0 +1,26 @@ +package dev.asodesu.islandutils.api.discord + +import dev.asodesu.islandutils.api.discord.container.DiscordContainer +import dev.asodesu.islandutils.api.discord.container.LayeredDiscordContainer +import dev.asodesu.islandutils.api.discord.container.SimpleDiscordContainer +import io.github.vyfor.kpresence.rpc.ActivityBuilder + +class ActivityContainerBuilder(private val layers: MutableList = mutableListOf()) { + fun layer(layer: T): T { + layers += layer + return layer + } + + fun build() = LayeredDiscordContainer(layers) +} + +typealias ActivityContainerBuilderFunction = ActivityContainerBuilder.() -> Unit +fun ActivityContainerBuilderFunction.build(): DiscordContainer { + return ActivityContainerBuilder().also { this.invoke(it) }.build() +} +fun Discord(func: ActivityContainerBuilderFunction): ActivityContainerBuilderFunction { + return func +} +fun DiscordActivity(func: ActivityBuilder.() -> Unit): SimpleDiscordContainer { + return SimpleDiscordContainer(func) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/ActivityLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/ActivityLayer.kt new file mode 100644 index 00000000..09b45d5c --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/ActivityLayer.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.discord + +import dev.asodesu.islandutils.api.events.EventConsumer +import io.github.vyfor.kpresence.rpc.ActivityBuilder + +interface ActivityLayer : EventConsumer { + fun update(activity: ActivityBuilder) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/DiscordContainerOverride.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/DiscordContainerOverride.kt new file mode 100644 index 00000000..5fdd30cc --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/DiscordContainerOverride.kt @@ -0,0 +1,10 @@ +package dev.asodesu.islandutils.api.discord + +import dev.asodesu.islandutils.api.discord.container.DiscordContainer + +class DiscordContainerOverride( + val reason: String, + val priority: Int, + val check: () -> Boolean, + val create: () -> DiscordContainer +) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/DiscordManager.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/DiscordManager.kt new file mode 100644 index 00000000..8aef22f2 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/DiscordManager.kt @@ -0,0 +1,89 @@ +package dev.asodesu.islandutils.api.discord + +import dev.asodesu.islandutils.api.discord.container.DiscordContainer +import dev.asodesu.islandutils.api.discord.container.EmptyDiscordContainer +import dev.asodesu.islandutils.api.events.MultiEventConsumerWrapper +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.modules.Module +import dev.asodesu.islandutils.api.server.ServerEvents +import dev.asodesu.islandutils.api.server.ServerSessionHandler +import dev.asodesu.islandutils.options.DiscordOptions +import io.github.vyfor.kpresence.RichClient +import io.github.vyfor.kpresence.rpc.Activity + +object DiscordManager : Module("DiscordPresence") { + val client = RichClient(1027930697417101344) + private val overrides = mutableListOf() + + private val containerEventWrapper = MultiEventConsumerWrapper { container.eventChildren() } + private var container: DiscordContainer = DiscordContainer.empty + set(value) { + field = value + this.update() + } + + override fun init() { + containerEventWrapper.registerEventHandlers() + client.connect(shouldBlock = false) + + addOverride( + reason = "disabled in config", + priority = 10, + check = { !DiscordOptions.enabled.get() }, + create = { EmptyDiscordContainer } + ) + addOverride( + reason = "big rat", + priority = 9, + check = { !ServerSessionHandler.isProduction }, + create = { DiscordContainer.bigRat } + ) + addOverride( + reason = "disabled place", + priority = 8, + check = { !DiscordOptions.showPlace.get() }, + create = { DiscordContainer.default.build() } + ) + + ServerEvents.SERVER_DISCONNECT.register { + resetContainer() + } + } + + fun addOverride(reason: String, priority: Int, check: () -> Boolean, create: () -> DiscordContainer) { + overrides.add(DiscordContainerOverride(reason, priority, check, create)) + overrides.sortBy { it.priority } + } + fun updateOverride(): Boolean { + val override = overrides.firstOrNull { it.check() } + if (override != null) { + this.container = override.create() + debug("Discord activity overridden, ${override.reason}") + return true + } + return false + } + + fun pushContainer(container: DiscordContainer) { + if (updateOverride()) return + + this.container = container + debug("Updated Discord activity to $container") + } + fun resetContainer() { + this.container = DiscordContainer.empty + debug("Reset Discord activity") + } + + fun update() = container.update() + + fun updateActivity(activity: Activity) { + if (!DiscordOptions.enabled.get()) return + client.update(activity) + logger.info("Updated activity") + } + fun clearActivity() { + client.clear() + logger.info("Cleared activity") + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/DiscordContainer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/DiscordContainer.kt new file mode 100644 index 00000000..3d0658bb --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/DiscordContainer.kt @@ -0,0 +1,33 @@ +package dev.asodesu.islandutils.api.discord.container + +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.DiscordActivity +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.timeElapsed +import dev.asodesu.islandutils.api.events.EventConsumer +import dev.asodesu.islandutils.api.server.ServerSessionHandler +import kotlin.time.Duration.Companion.hours + +interface DiscordContainer { + fun update() + fun eventChildren(): List + + companion object { + val empty = EmptyDiscordContainer + val default = Discord { + images("hub", "mcci") + timeElapsed(ServerSessionHandler.joinTime) + } + val bigRat = DiscordActivity { + assets { + largeImage = "bigrat" + largeText = "BIG RAT" + } + state = "BIG RAT BIG RAT BIG RAT" + details = "BIG RAT BIG RAT BIG RAT" + timestamps { + start = System.currentTimeMillis() - 24.hours.inWholeMilliseconds + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/EmptyDiscordContainer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/EmptyDiscordContainer.kt new file mode 100644 index 00000000..b24dc4f2 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/EmptyDiscordContainer.kt @@ -0,0 +1,13 @@ +package dev.asodesu.islandutils.api.discord.container + +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.events.EventConsumer + +object EmptyDiscordContainer : DiscordContainer { + override fun eventChildren() = emptyList() + override fun update() { + DiscordManager.clearActivity() + } + + override fun toString() = "EmptyDiscordContainer" +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/LayeredDiscordContainer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/LayeredDiscordContainer.kt new file mode 100644 index 00000000..01556b14 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/LayeredDiscordContainer.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.api.discord.container + +import dev.asodesu.islandutils.api.discord.ActivityLayer +import dev.asodesu.islandutils.api.discord.DiscordManager +import io.github.vyfor.kpresence.rpc.ActivityBuilder + +class LayeredDiscordContainer(val layers: List = emptyList()) : DiscordContainer { + override fun eventChildren() = layers + + override fun update() { + val activity = ActivityBuilder() + layers.forEach { it.update(activity) } + DiscordManager.updateActivity(activity.build()) + } + + override fun toString() = "LayeredDiscordContainer(layers=[${layers.joinToString(",") { it::class.simpleName.toString() }}])" +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/SimpleDiscordContainer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/SimpleDiscordContainer.kt new file mode 100644 index 00000000..2bd6e144 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/container/SimpleDiscordContainer.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.api.discord.container + +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.events.EventConsumer +import io.github.vyfor.kpresence.rpc.ActivityBuilder + +class SimpleDiscordContainer(val func: ActivityBuilder.() -> Unit) : DiscordContainer { + override fun eventChildren() = emptyList() + + override fun update() { + val activity = ActivityBuilder() + activity.func() + DiscordManager.updateActivity(activity.build()) + } + + override fun toString() = "SimpleDiscordContainer" +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/DetailsLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/DetailsLayer.kt new file mode 100644 index 00000000..6f0a06ad --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/DetailsLayer.kt @@ -0,0 +1,15 @@ +package dev.asodesu.islandutils.api.discord.layers + +import dev.asodesu.islandutils.api.discord.ActivityContainerBuilder +import dev.asodesu.islandutils.api.discord.ActivityLayer +import io.github.vyfor.kpresence.rpc.ActivityBuilder + +fun ActivityContainerBuilder.details(text: String) = layer(DetailsLayer(text)) +fun ActivityContainerBuilder.playingDetails(text: String) = layer(DetailsLayer("Playing $text")) + + +class DetailsLayer(val details: String) : ActivityLayer { + override fun update(activity: ActivityBuilder) { + activity.details = details + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/ImageLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/ImageLayer.kt new file mode 100644 index 00000000..1ca2f9ef --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/ImageLayer.kt @@ -0,0 +1,21 @@ +package dev.asodesu.islandutils.api.discord.layers + +import dev.asodesu.islandutils.IslandUtils +import dev.asodesu.islandutils.api.discord.ActivityContainerBuilder +import dev.asodesu.islandutils.api.discord.ActivityLayer +import io.github.vyfor.kpresence.rpc.ActivityBuilder + +fun ActivityContainerBuilder.images(largeImage: String) = layer(ImageLayer(largeImage)) +fun ActivityContainerBuilder.images(largeImage: String, smallImage: String) = layer(ImageLayer(largeImage, smallImage)) + +class ImageLayer(val largeAsset: String, val smallAsset: String = "mcci") : ActivityLayer { + override fun update(activity: ActivityBuilder) { + activity.assets { + largeImage = largeAsset + largeText = "MCC Island" + + smallImage = smallAsset + smallText = "IslandUtils - ${IslandUtils.modVersion}" + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/SidebarStateLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/SidebarStateLayer.kt new file mode 100644 index 00000000..d11f755c --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/SidebarStateLayer.kt @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.api.discord.layers + +import dev.asodesu.islandutils.api.discord.ActivityContainerBuilder +import dev.asodesu.islandutils.api.discord.ActivityLayer +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.extentions.withValue +import io.github.vyfor.kpresence.rpc.ActivityBuilder +import net.minecraft.network.chat.Component + +fun ActivityContainerBuilder.sidebarStateLayer(pattern: String, prefix: String = "") = layer(SidebarStateLayer(Regex(pattern), prefix)) +fun ActivityContainerBuilder.roundState() = sidebarStateLayer("ROUNDS \\[(\\d*/\\d*)]", "Round: ") +fun ActivityContainerBuilder.playersRemainingState() = sidebarStateLayer("PLAYERS REMAINING: (\\d*/\\d*)", "Remaining: ") + +class SidebarStateLayer(private val regex: Regex, val prefix: String = "") : ActivityLayer { + var value: String? = null + + override fun update(activity: ActivityBuilder) { + activity.state = value + } + + override fun onSidebarLine(component: Component) { + regex.withValue(component) { + value = prefix + it + DiscordManager.update() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/StateLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/StateLayer.kt new file mode 100644 index 00000000..5e075cf4 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/StateLayer.kt @@ -0,0 +1,14 @@ +package dev.asodesu.islandutils.api.discord.layers + +import dev.asodesu.islandutils.api.discord.ActivityContainerBuilder +import dev.asodesu.islandutils.api.discord.ActivityLayer +import io.github.vyfor.kpresence.rpc.ActivityBuilder + +fun ActivityContainerBuilder.state(text: String) = state { text } +fun ActivityContainerBuilder.state(stateConsumer: () -> String?) = layer(StateLayer(stateConsumer)) + +class StateLayer(val stateConsumer: () -> String?) : ActivityLayer { + override fun update(activity: ActivityBuilder) { + activity.state = stateConsumer() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/TimeElapsedLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/TimeElapsedLayer.kt new file mode 100644 index 00000000..59368a69 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/TimeElapsedLayer.kt @@ -0,0 +1,15 @@ +package dev.asodesu.islandutils.api.discord.layers + +import dev.asodesu.islandutils.api.discord.ActivityContainerBuilder +import dev.asodesu.islandutils.api.discord.ActivityLayer +import io.github.vyfor.kpresence.rpc.ActivityBuilder +import io.github.vyfor.kpresence.rpc.ActivityTimestamps + +fun ActivityContainerBuilder.timeElapsed() = layer(TimeElapsedLayer()) +fun ActivityContainerBuilder.timeElapsed(timeStart: Long) = layer(TimeElapsedLayer(timeStart)) + +class TimeElapsedLayer(val timeStart: Long = System.currentTimeMillis()) : ActivityLayer { + override fun update(activity: ActivityBuilder) { + activity.timestamps = ActivityTimestamps(timeStart) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/TimeRemainingLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/TimeRemainingLayer.kt new file mode 100644 index 00000000..7d662998 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/discord/layers/TimeRemainingLayer.kt @@ -0,0 +1,38 @@ +package dev.asodesu.islandutils.api.discord.layers + +import dev.asodesu.islandutils.api.discord.ActivityContainerBuilder +import dev.asodesu.islandutils.api.discord.ActivityLayer +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.extentions.debug +import io.github.vyfor.kpresence.rpc.ActivityBuilder +import io.github.vyfor.kpresence.rpc.ActivityTimestamps +import java.util.* +import net.minecraft.network.chat.Component + +fun ActivityContainerBuilder.timeRemaining() = layer(TimeRemainingLayer()) + +class TimeRemainingLayer() : ActivityLayer { + private val timerRegex = Regex("(\\d+):(\\d+)") + var startingTime = System.currentTimeMillis() + var endingTime = System.currentTimeMillis() + + override fun update(activity: ActivityBuilder) { + activity.timestamps = ActivityTimestamps(startingTime, endingTime) + } + + override fun onBossbarContents(uuid: UUID, contents: Component) { + val string = contents.string + debug("Got bossbar contents with a length of ${string.length}") + // check for if this bossbar actually contains a timer + if (!string.contains(":")) return + + val match = timerRegex.find(string) ?: return + val mins = match.groupValues.getOrNull(1)?.toIntOrNull() ?: return + val secs = match.groupValues.getOrNull(2)?.toIntOrNull() ?: return + + // this is how time works!! + val secondsLeft = (mins * 60L) + secs + 1 + endingTime = System.currentTimeMillis() + (secondsLeft * 1000) + DiscordManager.update() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/EventConsumer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/EventConsumer.kt new file mode 100644 index 00000000..f7e2c59f --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/EventConsumer.kt @@ -0,0 +1,69 @@ +package dev.asodesu.islandutils.api.events + +import com.noxcrew.noxesium.core.mcc.ClientboundMccGameStatePacket +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.events.bossbar.BossbarContentsUpdate +import dev.asodesu.islandutils.api.events.bossbar.BossbarEvents +import dev.asodesu.islandutils.api.events.sidebar.SidebarEvents +import dev.asodesu.islandutils.api.events.sidebar.SidebarLineUpdate +import dev.asodesu.islandutils.api.events.sound.SoundEvents +import dev.asodesu.islandutils.api.events.sound.SoundPlayCallback +import dev.asodesu.islandutils.api.events.sound.SoundStopCallback +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.GameEvents +import dev.asodesu.islandutils.api.game.events.GameChangeCallback +import dev.asodesu.islandutils.api.game.events.GameStateUpdateCallback +import dev.asodesu.islandutils.api.game.events.ServerUpdateCallback +import dev.asodesu.islandutils.api.server.InstanceJoinCallback +import dev.asodesu.islandutils.api.server.ServerDisconnectCallback +import dev.asodesu.islandutils.api.server.ServerEvents +import dev.asodesu.islandutils.api.server.ServerJoinCallback +import java.util.* +import net.minecraft.network.chat.Component + +interface EventConsumer : + GameChangeCallback, + GameStateUpdateCallback, + ServerUpdateCallback, + SidebarLineUpdate, + SoundPlayCallback, + SoundStopCallback, + BossbarContentsUpdate, + ServerJoinCallback, + ServerDisconnectCallback, + InstanceJoinCallback +{ + fun registerEventHandlers() { + GameEvents.GAME_CHANGE.register(this) + GameEvents.GAME_STATE_UPDATE.register(this) + GameEvents.SERVER_UPDATE.register(this) + SidebarEvents.LINE_UPDATE.register(this) + SoundEvents.SOUND_PLAY.register(this) + SoundEvents.SOUND_STOP.register(this) + BossbarEvents.BOSSBAR_CONTENTS_UPDATE.register(this) + ServerEvents.SERVER_JOIN.register(this) + ServerEvents.INSTANCE_JOIN.register(this) + ServerEvents.SERVER_DISCONNECT.register(this) + } + + // GameEvents + override fun onGameChange(from: Game, to: Game) {} + override fun onGameStateUpdate(from: ClientboundMccGameStatePacket, to: ClientboundMccGameStatePacket) {} + override fun onServerUpdate(packet: ClientboundMccServerPacket) {} + + // SidebarEvents + override fun onSidebarLine(component: Component) {} + + // SoundEvents + override fun onSoundPlay(info: SoundInfo, ci: SoundPlayCallback.Info) {} + override fun onSoundStop(info: SoundStopCallback.StopInfo, ci: SoundStopCallback.Info) {} + + // BossbarEvents + override fun onBossbarContents(uuid: UUID, contents: Component) {} + + // ServerEvents + override fun onServerConnect() {} + override fun onInstanceSwitch() {} + override fun onServerDisconnect() {} +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/EventConsumerWrapper.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/EventConsumerWrapper.kt new file mode 100644 index 00000000..d3c790a4 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/EventConsumerWrapper.kt @@ -0,0 +1,44 @@ +package dev.asodesu.islandutils.api.events + +import com.noxcrew.noxesium.core.mcc.ClientboundMccGameStatePacket +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.events.sound.SoundPlayCallback +import dev.asodesu.islandutils.api.events.sound.SoundStopCallback +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo +import dev.asodesu.islandutils.api.game.Game +import java.util.* +import net.minecraft.network.chat.Component + +interface EventConsumerWrapper : EventConsumer { + val consumer: EventConsumer + override fun onGameChange(from: Game, to: Game) { + consumer.onGameChange(from, to) + } + override fun onGameStateUpdate(from: ClientboundMccGameStatePacket, to: ClientboundMccGameStatePacket) { + consumer.onGameStateUpdate(from, to) + } + override fun onServerUpdate(packet: ClientboundMccServerPacket) { + consumer.onServerUpdate(packet) + } + override fun onSidebarLine(component: Component) { + consumer.onSidebarLine(component) + } + override fun onSoundPlay(info: SoundInfo, ci: SoundPlayCallback.Info) { + consumer.onSoundPlay(info, ci) + } + override fun onSoundStop(info: SoundStopCallback.StopInfo, ci: SoundStopCallback.Info) { + consumer.onSoundStop(info, ci) + } + override fun onBossbarContents(uuid: UUID, contents: Component) { + consumer.onBossbarContents(uuid, contents) + } + override fun onServerConnect() { + consumer.onServerConnect() + } + override fun onInstanceSwitch() { + consumer.onInstanceSwitch() + } + override fun onServerDisconnect() { + consumer.onServerDisconnect() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/EventExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/EventExt.kt new file mode 100644 index 00000000..81cfa7ca --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/EventExt.kt @@ -0,0 +1,13 @@ +package dev.asodesu.islandutils.api.events + +import net.fabricmc.fabric.api.event.EventFactory + +inline fun arrayBackedEvent(noinline invoker: (Array) -> T) = + EventFactory.createArrayBacked(T::class.java, invoker) + +fun EventConsumerWrapper(consumer: () -> EventConsumer) = object : EventConsumerWrapper { + override val consumer: EventConsumer get() = consumer() +} +fun MultiEventConsumerWrapper(children: () -> List) = object : MultiEventConsumerWrapper { + override fun children() = children() +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/MultiEventConsumerWrapper.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/MultiEventConsumerWrapper.kt new file mode 100644 index 00000000..582d6fcc --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/MultiEventConsumerWrapper.kt @@ -0,0 +1,44 @@ +package dev.asodesu.islandutils.api.events + +import com.noxcrew.noxesium.core.mcc.ClientboundMccGameStatePacket +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.events.sound.SoundPlayCallback +import dev.asodesu.islandutils.api.events.sound.SoundStopCallback +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo +import dev.asodesu.islandutils.api.game.Game +import java.util.* +import net.minecraft.network.chat.Component + +interface MultiEventConsumerWrapper : EventConsumer { + fun children(): Iterable + override fun onGameChange(from: Game, to: Game) { + children().forEach { it.onGameChange(from, to) } + } + override fun onGameStateUpdate(from: ClientboundMccGameStatePacket, to: ClientboundMccGameStatePacket) { + children().forEach { it.onGameStateUpdate(from, to) } + } + override fun onServerUpdate(packet: ClientboundMccServerPacket) { + children().forEach { it.onServerUpdate(packet) } + } + override fun onSidebarLine(component: Component) { + children().forEach { it.onSidebarLine(component) } + } + override fun onSoundPlay(info: SoundInfo, ci: SoundPlayCallback.Info) { + children().forEach { it.onSoundPlay(info, ci) } + } + override fun onSoundStop(info: SoundStopCallback.StopInfo, ci: SoundStopCallback.Info) { + children().forEach { it.onSoundStop(info, ci) } + } + override fun onBossbarContents(uuid: UUID, contents: Component) { + children().forEach { it.onBossbarContents(uuid, contents) } + } + override fun onServerDisconnect() { + children().forEach { it.onServerDisconnect() } + } + override fun onInstanceSwitch() { + children().forEach { it.onInstanceSwitch() } + } + override fun onServerConnect() { + children().forEach { it.onServerConnect() } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/bossbar/BossbarContentsUpdate.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/bossbar/BossbarContentsUpdate.kt new file mode 100644 index 00000000..155a7ca3 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/bossbar/BossbarContentsUpdate.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.events.bossbar + +import java.util.* +import net.minecraft.network.chat.Component + +fun interface BossbarContentsUpdate { + fun onBossbarContents(uuid: UUID, contents: Component) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/bossbar/BossbarEvents.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/bossbar/BossbarEvents.kt new file mode 100644 index 00000000..6ca60668 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/bossbar/BossbarEvents.kt @@ -0,0 +1,13 @@ +package dev.asodesu.islandutils.api.events.bossbar + +import dev.asodesu.islandutils.api.events.arrayBackedEvent + +object BossbarEvents { + + val BOSSBAR_CONTENTS_UPDATE = arrayBackedEvent { callbacks -> + BossbarContentsUpdate { uuid, component -> + callbacks.forEach { it.onBossbarContents(uuid, component) } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sidebar/SidebarEvents.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sidebar/SidebarEvents.kt new file mode 100644 index 00000000..e5175836 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sidebar/SidebarEvents.kt @@ -0,0 +1,13 @@ +package dev.asodesu.islandutils.api.events.sidebar + +import dev.asodesu.islandutils.api.events.arrayBackedEvent + +object SidebarEvents { + + val LINE_UPDATE = arrayBackedEvent { callbacks -> + SidebarLineUpdate { component -> + callbacks.forEach { it.onSidebarLine(component) } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sidebar/SidebarLineUpdate.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sidebar/SidebarLineUpdate.kt new file mode 100644 index 00000000..5230ae65 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sidebar/SidebarLineUpdate.kt @@ -0,0 +1,7 @@ +package dev.asodesu.islandutils.api.events.sidebar + +import net.minecraft.network.chat.Component + +fun interface SidebarLineUpdate { + fun onSidebarLine(component: Component) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundEvents.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundEvents.kt new file mode 100644 index 00000000..f9bf6bc1 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundEvents.kt @@ -0,0 +1,19 @@ +package dev.asodesu.islandutils.api.events.sound + +import dev.asodesu.islandutils.api.events.arrayBackedEvent + +object SoundEvents { + @JvmStatic + val SOUND_PLAY = arrayBackedEvent { callbacks -> + SoundPlayCallback { info, ci -> + callbacks.forEach { it.onSoundPlay(info, ci) } + } + } + + @JvmStatic + val SOUND_STOP = arrayBackedEvent { callbacks -> + SoundStopCallback { info, ci -> + callbacks.forEach { it.onSoundStop(info, ci) } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundPlayCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundPlayCallback.kt new file mode 100644 index 00000000..dfbb7a02 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundPlayCallback.kt @@ -0,0 +1,12 @@ +package dev.asodesu.islandutils.api.events.sound + +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo + +fun interface SoundPlayCallback { + fun onSoundPlay(info: SoundInfo, ci: Info) + + interface Info { + fun replace(info: SoundInfo) + fun cancel() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundStopCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundStopCallback.kt new file mode 100644 index 00000000..2b040530 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/SoundStopCallback.kt @@ -0,0 +1,13 @@ +package dev.asodesu.islandutils.api.events.sound + +import net.minecraft.resources.Identifier +import net.minecraft.sounds.SoundSource + +fun interface SoundStopCallback { + fun onSoundStop(info: StopInfo, ci: Info) + + data class StopInfo(val name: Identifier?, val source: SoundSource?) + interface Info { + fun cancel() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/ImmutableSoundInfo.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/ImmutableSoundInfo.kt new file mode 100644 index 00000000..15a9f9ea --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/ImmutableSoundInfo.kt @@ -0,0 +1,15 @@ +package dev.asodesu.islandutils.api.events.sound.info + +import net.minecraft.resources.Identifier +import net.minecraft.sounds.SoundSource + +class ImmutableSoundInfo( + override val sound: Identifier, + override val category: SoundSource = SoundSource.MASTER, + override val pitch: Float, + override val volume: Float, + override val fixedRange: Float? = null, + override val loop: Boolean = false +) : SoundInfo { + constructor(from: SoundInfo) : this(from.sound, from.category, from.pitch, from.volume, from.fixedRange, from.loop) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/MutableSoundInfo.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/MutableSoundInfo.kt new file mode 100644 index 00000000..9a985e91 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/MutableSoundInfo.kt @@ -0,0 +1,15 @@ +package dev.asodesu.islandutils.api.events.sound.info + +import net.minecraft.resources.Identifier +import net.minecraft.sounds.SoundSource + +class MutableSoundInfo( + override var sound: Identifier, + override var category: SoundSource = SoundSource.MASTER, + override var pitch: Float, + override var volume: Float, + override var fixedRange: Float? = null, + override var loop: Boolean = false +) : SoundInfo { + constructor(from: SoundInfo) : this(from.sound, from.category, from.pitch, from.volume, from.fixedRange, from.loop) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/SoundInfo.kt b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/SoundInfo.kt new file mode 100644 index 00000000..93a06fa8 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/events/sound/info/SoundInfo.kt @@ -0,0 +1,33 @@ +package dev.asodesu.islandutils.api.events.sound.info + +import java.util.* +import net.minecraft.network.protocol.game.ClientboundSoundPacket +import net.minecraft.resources.Identifier +import net.minecraft.sounds.SoundEvent +import net.minecraft.sounds.SoundSource +import kotlin.jvm.optionals.getOrNull + +interface SoundInfo { + val sound: Identifier + val category: SoundSource + val pitch: Float + val volume: Float + val fixedRange: Float? + val loop: Boolean + + fun toSoundEvent() = SoundEvent(sound, Optional.ofNullable(fixedRange)) + + fun toImmutable() = ImmutableSoundInfo(this) + fun toMutable() = MutableSoundInfo(this) + + companion object { + fun fromPacket(packet: ClientboundSoundPacket) = ImmutableSoundInfo( + sound = packet.sound.value().location, + category = packet.source, + pitch = packet.pitch, + volume = packet.volume, + fixedRange = packet.sound.value().fixedRange.getOrNull(), + loop = false + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/DebugExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/DebugExt.kt new file mode 100644 index 00000000..2e07e694 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/DebugExt.kt @@ -0,0 +1,42 @@ +package dev.asodesu.islandutils.api.extentions + +import com.mojang.blaze3d.platform.InputConstants +import dev.asodesu.islandutils.api.chest.analysis.ContainerScreenHelper +import dev.asodesu.islandutils.api.chest.customItemId +import dev.asodesu.islandutils.options.MiscOptions +import java.util.function.Consumer +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import org.slf4j.LoggerFactory + +val debugMode by MiscOptions.Debug.debugMode + +private val logger = LoggerFactory.getLogger("IU-DEBUG") +fun debug(str: String) { + if (debugMode) send(Component.literal("[IslandUtils] $str").withStyle(ChatFormatting.GRAY)) + else logger.info(str) +} + +fun addDebugTooltip(item: ItemStack, consumer: Consumer) { + if (!debugMode) return + if (!InputConstants.isKeyDown(minecraft.window, InputConstants.KEY_LCONTROL)) return + + val customItemId = item.customItemId + consumer.accept(buildComponent { + // add custom item id + if (customItemId != null) { + append(Component.literal(customItemId.toString()).style { withColor(ChatFormatting.AQUA) }) + } + + // get the current container screen + if (minecraft.screen is AbstractContainerScreen<*>) { + val hoveredSlot = (minecraft.screen as ContainerScreenHelper).getHoveredSlot() ?: return@buildComponent + if (hoveredSlot.item == item) { + if (customItemId != null) append(" ") + append(Component.literal("(${hoveredSlot.containerSlot})").style { withColor(ChatFormatting.GRAY) }) + } + } + }) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/DurationExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/DurationExt.kt new file mode 100644 index 00000000..f1151818 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/DurationExt.kt @@ -0,0 +1,6 @@ +package dev.asodesu.islandutils.api.extentions + +import kotlin.time.Duration + +val Duration.ticks: Int + get() = this.inWholeMilliseconds.toInt() / 50 \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/FabricExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/FabricExt.kt new file mode 100644 index 00000000..49e91819 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/FabricExt.kt @@ -0,0 +1,7 @@ +package dev.asodesu.islandutils.api.extentions + +import java.nio.file.Path +import net.fabricmc.loader.api.FabricLoader + +val configDir: Path = FabricLoader.getInstance().configDir +val islandUtilsFolder: Path = configDir.resolve("islandutils") \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/GuiExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/GuiExt.kt new file mode 100644 index 00000000..05fcf426 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/GuiExt.kt @@ -0,0 +1,62 @@ +package dev.asodesu.islandutils.api.extentions + +import net.minecraft.client.MouseHandler +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.MutableComponent +import net.minecraft.network.chat.Style +import net.minecraft.world.phys.Vec3 +import org.joml.Matrix3x2fStack + +/** + * Draws a rectangle at the coordinates with the given width and height + */ +fun GuiGraphics.rect(x: Int, y: Int, width: Int, height: Int, color: Int) { + this.fill(x, y, x + width, y + height, color) +} + +inline fun GuiGraphics.scissor(x: Int, y: Int, width: Int, height: Int, apply: () -> Unit) { + this.enableScissor(x, y, x + width, y + height) + apply() + this.disableScissor() +} + +inline fun GuiGraphics.pose(apply: Matrix3x2fStack.() -> Unit) { + pose().pushMatrix() + apply(pose()) + pose().popMatrix() +} + +fun MouseHandler.isInsideBox(x: Int, y: Int, width: Int, height: Int): Boolean { + val window = minecraft.window + val mouseX = this.getScaledXPos(window).toInt() + val mouseY = this.getScaledYPos(window).toInt() + return isInsideBox(mouseX, mouseY, x, y, width, height) +} + +fun isInsideBox(targetX: Int, targetY: Int, x: Int, y: Int, width: Int, height: Int): Boolean { + val xBound = x + width + val yBound = y + height + return targetX >= x && targetY >= y && targetX <= xBound && targetY <= yBound +} + +fun vecRgb(red: Int, green: Int, blue: Int) = Vec3(red / 255.0, green / 255.0, blue / 255.0) + +fun MutableComponent.appendLine(component: Component): MutableComponent { + return this.append(component).newLine() +} + +fun MutableComponent.newLine(): MutableComponent { + return this.append("\n") +} + +inline fun MutableComponent.copyAndStyle(crossinline func: Style.() -> Style): Component { + return this.copy().withStyle { it.func() } +} +inline fun MutableComponent.style(crossinline func: Style.() -> Style): Component { + return this.withStyle { it.func() } +} + +inline fun buildComponent(func: MutableComponent.() -> Unit): Component { + return Component.empty().also(func) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/HashExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/HashExt.kt new file mode 100644 index 00000000..3953797b --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/HashExt.kt @@ -0,0 +1,21 @@ +package dev.asodesu.islandutils.api.extentions + +import dev.asodesu.islandutils.IslandUtils +import java.math.BigInteger +import java.nio.charset.StandardCharsets +import java.security.MessageDigest + +fun String.calculateHash(messageDigest: MessageDigest): String { + return try { + messageDigest.update(this.toByteArray(StandardCharsets.UTF_8)) + val digest = messageDigest.digest() + "%064x".format(BigInteger(1, digest)) + } catch (e: Exception) { + IslandUtils.logger.error("Failed to calculate ${messageDigest.algorithm} hash for $this", e) + "" + } +} + +fun String.calculateSha256(): String { + return this.calculateHash(MessageDigest.getInstance("SHA-256")) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/MinecraftExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/MinecraftExt.kt new file mode 100644 index 00000000..0cbc1fe6 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/MinecraftExt.kt @@ -0,0 +1,50 @@ +package dev.asodesu.islandutils.api.extentions + +import dev.asodesu.islandutils.api.server.ServerSessionHandler +import net.minecraft.client.Minecraft +import net.minecraft.client.multiplayer.ClientPacketListener +import net.minecraft.client.multiplayer.chat.ChatListener +import net.minecraft.client.resources.sounds.SimpleSoundInstance +import net.minecraft.client.sounds.SoundManager +import net.minecraft.network.chat.Component +import net.minecraft.resources.Identifier +import net.minecraft.sounds.SoundEvent + +/** + * The current minecraft client instance + */ +val minecraft: Minecraft + get() = Minecraft.getInstance() + +/** + * Minecraft Chat Listener + */ +val chatListener: ChatListener + get() = minecraft.chatListener + +/** + * Sends a chat message to player + */ +fun send(component: Component) { + chatListener.handleSystemMessage(component, false) +} + +object Resources { + fun islandUtils(path: String): Identifier = Identifier.fromNamespaceAndPath("islandutils", path) + fun mcc(path: String): Identifier = Identifier.fromNamespaceAndPath("mcc", path) +} + +fun Identifier.toSoundEvent() = SoundEvent.createVariableRangeEvent(this) +fun SoundManager.play(identifier: Identifier, volume: Float = 1f, pitch: Float = 1f) = this.play(identifier.toSoundEvent(), volume, pitch) +fun SoundManager.play(event: SoundEvent, volume: Float = 1f, pitch: Float = 1f) = this.play(SimpleSoundInstance.forUI(event, pitch, volume)) + +/** + * The current active server connection + */ +val connection: ClientPacketListener? + get() = minecraft.connection + +/** + * If the player is currently online on MCC Island + */ +val isOnline: Boolean get() = ServerSessionHandler.isOnline \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/NoxesiumExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/NoxesiumExt.kt new file mode 100644 index 00000000..79246983 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/NoxesiumExt.kt @@ -0,0 +1,14 @@ +package dev.asodesu.islandutils.api.extentions + +import com.noxcrew.noxesium.core.mcc.ClientboundMccGameStatePacket + +fun nullGameState() = ClientboundMccGameStatePacket("none", "unknown_queue", "unknown_phase", "unknown_stage", -1, -1, "unknown_map", "Unknown Map") + +object PhaseType { + const val LOADING = "LOADING" + const val WAITING_FOR_PLAYERS = "WAITING_FOR_PLAYERS" + const val PRE_GAME = "PRE_GAME" + const val PLAY = "PLAY" + const val INTERMISSION = "INTERMISSION" + const val POST_GAME = "POST_GAME" +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/RegexExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/RegexExt.kt new file mode 100644 index 00000000..7bd4abed --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/RegexExt.kt @@ -0,0 +1,10 @@ +package dev.asodesu.islandutils.api.extentions + +import net.minecraft.network.chat.Component + +fun Regex.withValue(component: Component, callback: (String) -> Unit) { + val plainString = component.string + val result = this.find(plainString) ?: return + val value = result.groupValues.getOrNull(1) ?: return + callback(value) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/extentions/TimeExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/TimeExt.kt new file mode 100644 index 00000000..dc4399e8 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/extentions/TimeExt.kt @@ -0,0 +1,29 @@ +package dev.asodesu.islandutils.api.extentions + +private val TIME_REGEX = "(\\d+)([dhms])".toRegex() + +/** + * Gets the amount of seconds in Island's time strings, formatted: + * + * "3h 34m 19s" + */ +fun String.getTimeSeconds(): Long? { + var time = 0L + + val findResult = TIME_REGEX.findAll(this) + + val iterator = findResult.iterator() + if (!iterator.hasNext()) return null + iterator.forEach { + val value = it.groupValues[1].toIntOrNull() ?: return null + + when (it.groupValues[2]) { + "d" -> time += value * 86400 // seconds in a day + "h" -> time += value * 3600 // seconds in an hour + "m" -> time += value * 60 // seconds in a minute + "s" -> time += value + } + } + + return time +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/Command.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/Command.kt new file mode 100644 index 00000000..1566bfdc --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/Command.kt @@ -0,0 +1,34 @@ +package dev.asodesu.islandutils.api.game + +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.extentions.debugMode +import java.util.concurrent.CompletableFuture +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource + +fun gamesDebugCommand() = literal("games") + .then(forceGameDebugCommand()) + +fun gameTypeSuggestions(ctx: CommandContext, builder: SuggestionsBuilder): CompletableFuture { + gameManager.gamesById.keys.forEach { builder.suggest(it) } + return CompletableFuture.completedFuture(builder.build()) +} + +private fun forceGameDebugCommand() = literal("set_game") + .then(argument("game_type", StringArgumentType.word()).suggests(::gameTypeSuggestions) + .executes { ctx -> + if (!debugMode) return@executes 0 + + val gameType = ctx.getArgument("game_type", String::class.javaObjectType) + val gameContext = gameManager.gamesById[gameType] ?: return@executes 0 + gameManager.setGame( + gameContext.create(ClientboundMccServerPacket("", listOf("barren_3", "main-1"))) + ) + + 1 + }) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/EmptyGame.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/EmptyGame.kt new file mode 100644 index 00000000..2e9d3e7c --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/EmptyGame.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.game + +import dev.asodesu.islandutils.api.discord.container.DiscordContainer + +object EmptyGame : Game("none", emptyList()) { + override val hasTeamChat = false + override val discord = DiscordContainer.default +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/Game.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/Game.kt new file mode 100644 index 00000000..e032d487 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/Game.kt @@ -0,0 +1,23 @@ +package dev.asodesu.islandutils.api.game + +import dev.asodesu.islandutils.api.discord.ActivityContainerBuilderFunction +import dev.asodesu.islandutils.api.events.EventConsumer +import org.slf4j.LoggerFactory + +/** + * A game, for managing game specific modules, music and discord presence. Initialised + * when the player enters the game, and a corresponding MCC Server packet is received. + * + * @param id An identifier for this game (only used within mod, + * doesn't need to match island counterpart) + */ +abstract class Game(val id: String, val types: List): EventConsumer { + protected val logger = LoggerFactory.getLogger("IU-Game'$id'") + abstract val hasTeamChat: Boolean + abstract val discord: ActivityContainerBuilderFunction + + /** + * Called when the player changes servers away from this game. + */ + open fun unregister() {} +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/GameEvents.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/GameEvents.kt new file mode 100644 index 00000000..535faae1 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/GameEvents.kt @@ -0,0 +1,35 @@ +package dev.asodesu.islandutils.api.game + +import dev.asodesu.islandutils.api.events.arrayBackedEvent +import dev.asodesu.islandutils.api.game.events.GameChangeCallback +import dev.asodesu.islandutils.api.game.events.GameStateUpdateCallback +import dev.asodesu.islandutils.api.game.events.ServerUpdateCallback + +object GameEvents { + /** + * Called when one game is unregistered and another takes it's place + */ + val GAME_CHANGE = arrayBackedEvent { callbacks -> + GameChangeCallback { from, to -> + callbacks.forEach { it.onGameChange(from, to) } + } + } + + /** + * When we receive an MCC Server Packet, BEFORE any game initialisation takes place. + */ + val SERVER_UPDATE = arrayBackedEvent { callbacks -> + ServerUpdateCallback { packet -> + callbacks.forEach { it.onServerUpdate(packet) } + } + } + + /** + * When we receive an MCC Game State Update Packet. + */ + val GAME_STATE_UPDATE = arrayBackedEvent { callbacks -> + GameStateUpdateCallback { from, to -> + callbacks.forEach { it.onGameStateUpdate(from, to) } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/GameExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/GameExt.kt new file mode 100644 index 00000000..fc551a53 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/GameExt.kt @@ -0,0 +1,38 @@ +package dev.asodesu.islandutils.api.game + +import dev.asodesu.islandutils.Modules +import dev.asodesu.islandutils.games.Fishing +import dev.asodesu.islandutils.games.Hub + +/** + * The game manager + */ +val gameManager: GameManager + get() = Modules.gameManager + +/** + * The current game the player is in + */ +val activeGame: Game + get() = gameManager.active + +/** + * If the player is in the lobby, by checking if the + * current active game is `Hub` + */ +val inLobby: Boolean + get() = activeGame is Hub + +/** + * If the player is in the lobby or fishing, by checking if the + * current active game is `Hub` or `Fishing` + */ +val inLobbyOrFishing: Boolean + get() = activeGame is Hub || activeGame is Fishing + +/** + * If the player is in game, by checking if the current + * active game is not `Hub` + */ +val inGame: Boolean + get() = !inLobby \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/GameManager.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/GameManager.kt new file mode 100644 index 00000000..f4ac1789 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/GameManager.kt @@ -0,0 +1,78 @@ +package dev.asodesu.islandutils.api.game + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import com.noxcrew.noxesium.core.mcc.MccPackets +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.discord.build +import dev.asodesu.islandutils.api.events.EventConsumerWrapper +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.game.context.GameContext +import dev.asodesu.islandutils.api.modules.Module + +/** + * Manager for managing the active game, listens for MCC Server packets + * and updates active game accordingly + * + * @param contexts All the game contexts to be checked, can + * be ordered to define checking order + */ +class GameManager(vararg contexts: GameContext) : Module("GameManager") { + private val registeredGames = contexts.toMutableList() + val gamesById = registeredGames.associateBy { it.id } + + var active: Game = EmptyGame + private val activeGameEventHandler = EventConsumerWrapper { active } + var lastGameChange: Long = System.currentTimeMillis() + + override fun init() { + logger.info("GameManager initialised with ${registeredGames.size} games.") + + activeGameEventHandler.registerEventHandlers() + MccPackets.CLIENTBOUND_MCC_SERVER.addListener(this, ClientboundMccServerPacket::class.java) { _, packet, _ -> + this.onMccServer(packet) + } + } + + private fun onMccServer(packet: ClientboundMccServerPacket) { + debug("Server: $packet") + GameEvents.SERVER_UPDATE.invoker().onServerUpdate(packet) + + val context = registeredGames.firstOrNull { it.check(packet) } + if (context == null) { + resetGame() + debug("Couldn't create game with data $packet") + return + } + val newActiveGame = context.create(packet) + setGame(newActiveGame) + } + + fun setGame(newActiveGame: Game) { + active.unregister() + GameEvents.GAME_CHANGE.invoker().onGameChange(active, newActiveGame) + if (active::class != newActiveGame::class) { + lastGameChange = System.currentTimeMillis() + } + + debug("Set active game to $newActiveGame") + active = newActiveGame + this.pushDiscord() + } + + fun pushDiscord() { + try { + DiscordManager.pushContainer(active.discord.build()) + } catch (e: Exception) { + DiscordManager.resetContainer() + } + } + + private fun resetGame() { + debug("Reset active game") + active.unregister() + GameEvents.GAME_CHANGE.invoker().onGameChange(active, EmptyGame) + + active = EmptyGame + this.pushDiscord() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/context/GameContext.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/context/GameContext.kt new file mode 100644 index 00000000..d1f02062 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/context/GameContext.kt @@ -0,0 +1,34 @@ +package dev.asodesu.islandutils.api.game.context + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.game.Game + +/** + * The context for creating `Game` objects based on incoming MCC Server packets + */ +interface GameContext { + /** + * A unique identifier for this game, used for debugging + */ + val id: String + + /** + * Checks if this game should be started by this packet. + * + * @param packet The incoming MCC Server packet + * @return true if this game is defined in this packet + */ + fun check(packet: ClientboundMccServerPacket): Boolean + + /** + * Creates a new instance of this game. + * + * @param packet The incoming MCC Server packet + * @return A new instance of this game + */ + fun create(packet: ClientboundMccServerPacket): Game + + fun ClientboundMccServerPacket.checkTypes(vararg type: String) = this.types.containsAll(type.toList()) + fun ClientboundMccServerPacket.checkGame(vararg type: String) = this.server == "game" && checkTypes(*type) + fun ClientboundMccServerPacket.checkLobby(vararg type: String) = this.server == "lobby" && checkTypes(*type) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/context/SimpleGameContext.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/context/SimpleGameContext.kt new file mode 100644 index 00000000..e9c02486 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/context/SimpleGameContext.kt @@ -0,0 +1,11 @@ +package dev.asodesu.islandutils.api.game.context + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket + +abstract class SimpleGameContext(private val islandId: String) : GameContext { + override val id = islandId + + override fun check(packet: ClientboundMccServerPacket): Boolean { + return packet.checkGame(islandId) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/events/GameChangeCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/events/GameChangeCallback.kt new file mode 100644 index 00000000..ab2ee8a4 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/events/GameChangeCallback.kt @@ -0,0 +1,7 @@ +package dev.asodesu.islandutils.api.game.events + +import dev.asodesu.islandutils.api.game.Game + +fun interface GameChangeCallback { + fun onGameChange(from: Game, to: Game) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/events/GameStateUpdateCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/events/GameStateUpdateCallback.kt new file mode 100644 index 00000000..8ebcbc4f --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/events/GameStateUpdateCallback.kt @@ -0,0 +1,7 @@ +package dev.asodesu.islandutils.api.game.events + +import com.noxcrew.noxesium.core.mcc.ClientboundMccGameStatePacket + +fun interface GameStateUpdateCallback { + fun onGameStateUpdate(from: ClientboundMccGameStatePacket, to: ClientboundMccGameStatePacket) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/events/ServerUpdateCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/events/ServerUpdateCallback.kt new file mode 100644 index 00000000..cb6dbc3e --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/events/ServerUpdateCallback.kt @@ -0,0 +1,7 @@ +package dev.asodesu.islandutils.api.game.events + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket + +fun interface ServerUpdateCallback { + fun onServerUpdate(packet: ClientboundMccServerPacket) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/game/state/StateManager.kt b/src/main/kotlin/dev/asodesu/islandutils/api/game/state/StateManager.kt new file mode 100644 index 00000000..fdc66127 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/game/state/StateManager.kt @@ -0,0 +1,24 @@ +package dev.asodesu.islandutils.api.game.state + +import com.noxcrew.noxesium.core.mcc.ClientboundMccGameStatePacket +import com.noxcrew.noxesium.core.mcc.MccPackets +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.extentions.nullGameState +import dev.asodesu.islandutils.api.game.GameEvents +import dev.asodesu.islandutils.api.modules.Module + +object StateManager : Module("StateManager") { + var current = nullGameState() + + override fun init() { + MccPackets.CLIENTBOUND_MCC_GAME_STATE.addListener(this, ClientboundMccGameStatePacket::class.java) { _, packet, _ -> + this.onMccGameState(packet) + } + } + + private fun onMccGameState(packet: ClientboundMccGameStatePacket) { + debug("Game State: $packet") + GameEvents.GAME_STATE_UPDATE.invoker().onGameStateUpdate(current, packet) + current = packet + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/json/ComponentSerializer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/json/ComponentSerializer.kt new file mode 100644 index 00000000..395f3135 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/json/ComponentSerializer.kt @@ -0,0 +1,9 @@ +package dev.asodesu.islandutils.api.json + +import com.mojang.serialization.Codec +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.ComponentSerialization + +object ComponentSerializer : JsonCodecSerializer() { + override val codec: Codec = ComponentSerialization.CODEC +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/json/ItemStackSerializer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/json/ItemStackSerializer.kt new file mode 100644 index 00000000..450a705e --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/json/ItemStackSerializer.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.json + +import com.mojang.serialization.Codec +import net.minecraft.world.item.ItemStack + +object ItemStackSerializer : JsonCodecSerializer() { + override val codec: Codec = ItemStack.SINGLE_ITEM_CODEC +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/json/JsonCodecSerializer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/json/JsonCodecSerializer.kt new file mode 100644 index 00000000..3ac03f24 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/json/JsonCodecSerializer.kt @@ -0,0 +1,29 @@ +package dev.asodesu.islandutils.api.json + +import com.google.gson.Gson +import com.google.gson.JsonElement +import com.mojang.serialization.Codec +import com.mojang.serialization.JsonOps +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.minecraft.util.GsonHelper + +abstract class JsonCodecSerializer : KSerializer { + override val descriptor: SerialDescriptor = String.serializer().descriptor + abstract val codec: Codec + private val gson = Gson() + + override fun deserialize(decoder: Decoder): T { + val jsonElement = GsonHelper.fromJson(gson, decoder.decodeString(), JsonElement::class.java) + val dataResult = codec.decode(JsonOps.INSTANCE, jsonElement) + return dataResult.orThrow.first + } + + override fun serialize(encoder: Encoder, value: T) { + val jsonElement = codec.encodeStart(JsonOps.INSTANCE, value).orThrow + encoder.encodeString(jsonElement.toString()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/json/JsonExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/json/JsonExt.kt new file mode 100644 index 00000000..d2781ff5 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/json/JsonExt.kt @@ -0,0 +1,23 @@ +package dev.asodesu.islandutils.api.json + +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.modules.SerializersModule +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack + +val JSON = Json { + serializersModule = SerializersModule { + contextual(Component::class, ComponentSerializer) + contextual(ItemStack::class, ItemStackSerializer) + } + ignoreUnknownKeys = true +} + +inline fun T.encode() = JSON.encodeToString(this) +inline fun T.encodeToJsonElement() = JSON.encodeToJsonElement(this) + +inline fun String.decode(): T = JSON.decodeFromString(this) +inline fun JsonElement.decodeFromJsonElement(): T = JSON.decodeFromJsonElement(this) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/modules/Module.kt b/src/main/kotlin/dev/asodesu/islandutils/api/modules/Module.kt new file mode 100644 index 00000000..332690a2 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/modules/Module.kt @@ -0,0 +1,12 @@ +package dev.asodesu.islandutils.api.modules + +import org.slf4j.LoggerFactory + +/** + * A module + * TODO: good documentation + */ +abstract class Module(name: String) { + protected val logger = LoggerFactory.getLogger("IU-${name}") + abstract fun init() +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/modules/ModuleManager.kt b/src/main/kotlin/dev/asodesu/islandutils/api/modules/ModuleManager.kt new file mode 100644 index 00000000..8f8e2ca7 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/modules/ModuleManager.kt @@ -0,0 +1,33 @@ +package dev.asodesu.islandutils.api.modules + +import dev.asodesu.islandutils.Modules +import org.slf4j.LoggerFactory +import kotlin.reflect.full.memberProperties + +object ModuleManager { + private val logger = LoggerFactory.getLogger("IU-ModuleManager") + private val modules = buildList { + // setup modules object + val instance = Modules::class.objectInstance ?: throw RuntimeException("Modules is not an object.") + Modules::class.memberProperties.forEach { + val obj = it.get(instance) + if (obj is Module) add(obj) + } + + addAll(Modules.objects) + } + + fun init() { + logger.info("Initialising modules...") + var successes = 0 + modules.forEach { + try { + it.init() + logger.info("Initialised ${it::class.simpleName}") + successes++ + } catch (e: Exception) { + logger.error("Failed to initialise module '${it::class.qualifiedName}'", e) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/LoopTag.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/LoopTag.kt new file mode 100644 index 00000000..e24a6bf7 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/LoopTag.kt @@ -0,0 +1,9 @@ +package dev.asodesu.islandutils.api.music + +/** + * A tag representing an action by the server on this sound + */ +enum class LoopTag { + STOPPED, + RESTARTED +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicManager.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicManager.kt new file mode 100644 index 00000000..c435a4b6 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicManager.kt @@ -0,0 +1,118 @@ +package dev.asodesu.islandutils.api.music + +import dev.asodesu.islandutils.api.events.sound.SoundEvents +import dev.asodesu.islandutils.api.events.sound.SoundPlayCallback +import dev.asodesu.islandutils.api.events.sound.SoundStopCallback +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.modules.Module +import dev.asodesu.islandutils.options.MusicOptions +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.minecraft.client.Minecraft + +class MusicManager(knownTracks: List, val modifiers: List) : Module("MusicManager") { + private val enabled by MusicOptions.enabled + + private val knownTracks: Map = knownTracks.associateBy { it.key } + private val playingSounds = mutableListOf() + + override fun init() { + ClientTickEvents.END_CLIENT_TICK.register(::tick) + SoundEvents.SOUND_PLAY.register(::handleSoundPlay) + SoundEvents.SOUND_STOP.register(::handleSoundStop) + + modifiers.forEach { + val download = it.downloadJob() ?: return@forEach + if (it.enableOption.get()) download.start() + } + } + + fun tick(client: Minecraft) { + val iterator = playingSounds.iterator() + iterator.forEach { + // loop prevention + val tags = it.handleTags() + // check if the server triggered a stop on this sound + if (tags != null && tags.contains(LoopTag.STOPPED)) { + // check if the server stopped, but didn't restart + if (!tags.contains(LoopTag.RESTARTED)) { + // the server didn't restart, fading... + it.fade() + iterator.remove() + debug("Fading ${it.unmodifiedSound}...") + } + } + + if (!minecraft.soundManager.isActive(it)) { + iterator.remove() + debug("Music track ${it.unmodifiedSound} stopped, removed") + } + } + } + + fun handleSoundPlay(info: SoundInfo, ci: SoundPlayCallback.Info) { + if (!enabled) return + if (info.sound.namespace != "mcc") return + + val soundLocation = info.sound + if (!soundLocation.path.startsWith("music.")) return + val trackInfo = knownTracks[soundLocation.path] ?: return // ignore music tracks that we don't care about + + val existingSounds = playingSounds.filter { it.unmodifiedSound == soundLocation } + if (existingSounds.isNotEmpty()) { + existingSounds.forEach { it.tag(LoopTag.RESTARTED) } + return ci.cancel() + } + + val newSound = info.toMutable() + newSound.loop = trackInfo.loop + modifiers.forEach { + if (it.shouldApply(info) && it.enableOption.get()) it.modify(newSound) + } + + val instance = MusicSoundInstance( + sound = newSound.sound, + pitch = newSound.pitch, + volume = newSound.volume, + category = newSound.category, + loop = newSound.loop, + + unmodifiedSound = soundLocation + ) + playingSounds += instance + minecraft.soundManager.play(instance) + debug("Playing ${soundLocation.path}[${newSound.sound.path}] as a looping track") + ci.cancel() + } + + fun handleSoundStop(packet: SoundStopCallback.StopInfo, ci: SoundStopCallback.Info) { + // functionality similar to that of vanilla stopping behaviour (defined in SoundEngine#stop) + val source = packet.source + val name = packet.name + val cancel = if (source != null) { + // source is set, name may or may not be set + // fade all sounds on this source, or on this source with this name + playingSounds.tagIf(LoopTag.STOPPED) { it.source == source && (name == null || it.unmodifiedSound == name) } + } else if (name == null) { + // source and name are null, fade all + playingSounds.tagIf(LoopTag.STOPPED) { true } + } else { + // source is null, name is set, fade all with this name + playingSounds.tagIf(LoopTag.STOPPED) { it.unmodifiedSound == name } + } + + if (cancel) ci.cancel() + } + + private fun MutableList.tagIf(tag: LoopTag, func: (MusicSoundInstance) -> Boolean): Boolean { + return this.any { + if (func(it)) { + it.tag(tag) + true + } else false + } + } + + class Track(val key: String, val loop: Boolean = true) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicModifier.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicModifier.kt new file mode 100644 index 00000000..7a0ea7d3 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicModifier.kt @@ -0,0 +1,14 @@ +package dev.asodesu.islandutils.api.music + +import dev.asodesu.islandutils.api.music.resources.handler.DownloadJob +import dev.asodesu.islandutils.api.options.option.Option +import dev.asodesu.islandutils.api.events.sound.info.MutableSoundInfo +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo + +interface MusicModifier { + val enableOption: Option + + fun downloadJob(): DownloadJob? = null + fun modify(info: MutableSoundInfo) + fun shouldApply(info: SoundInfo): Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicReplacementModifier.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicReplacementModifier.kt new file mode 100644 index 00000000..466ab2f8 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicReplacementModifier.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.api.music + +import dev.asodesu.islandutils.api.events.sound.info.MutableSoundInfo +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo +import net.minecraft.resources.Identifier + +interface MusicReplacementModifier : MusicModifier { + fun replace(server: Identifier): Identifier? + fun check(server: Identifier): Boolean + + override fun modify(info: MutableSoundInfo) { + replace(info.sound) + ?.let { info.sound = it } + } + + override fun shouldApply(info: SoundInfo) = check(info.sound) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicSoundInstance.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicSoundInstance.kt new file mode 100644 index 00000000..75b53b18 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/MusicSoundInstance.kt @@ -0,0 +1,75 @@ +package dev.asodesu.islandutils.api.music + +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.extentions.ticks +import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance +import net.minecraft.client.resources.sounds.SoundInstance +import net.minecraft.resources.Identifier +import net.minecraft.sounds.SoundEvent +import net.minecraft.sounds.SoundSource +import net.minecraft.util.Mth +import net.minecraft.util.RandomSource +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +class MusicSoundInstance( + sound: Identifier, + category: SoundSource, + loop: Boolean = false, + pitch: Float = 1f, + volume: Float = 1f, + + val unmodifiedSound: Identifier = sound +) : AbstractTickableSoundInstance(SoundEvent.createVariableRangeEvent(sound), category, randomSource) { + companion object { + private val randomSource = RandomSource.create() + } + + private var loopTags = mutableListOf() + private var fade: FadeTask? = null + + init { + this.pitch = pitch + this.volume = volume + this.attenuation = SoundInstance.Attenuation.NONE + this.looping = loop + } + + override fun tick() { + this.fade?.let { task -> + this.volume = task.tick() + if (task.isFinished) this.fade = null + if (this.volume <= 0f) this.stop() + } + } + + /** + * Returns and clears all loop tags on this instance + * + * @return all the loop tags + */ + fun handleTags(): List? { + if (loopTags.isEmpty()) return null + return loopTags.toList() + .also { loopTags.clear() } + } + + /** + * Adds a new LoopTag to this instance + */ + fun tag(loopTag: LoopTag) { + loopTags.add(loopTag) + debug("Tagged $unmodifiedSound with $loopTag") + } + + fun fade(duration: Duration = 0.5.seconds, to: Float = 0f) { + this.fade = FadeTask(this.volume, to, duration.ticks) + } + + class FadeTask(val start: Float, val end: Float, val duration: Int) { + private var ticks = 0 + val isFinished get() = ticks >= duration + fun tick() = Mth.lerp(++ticks / duration.toFloat(), start, end) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/DownloadScreen.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/DownloadScreen.kt new file mode 100644 index 00000000..68387b9d --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/DownloadScreen.kt @@ -0,0 +1,50 @@ +package dev.asodesu.islandutils.api.music.resources + +import dev.asodesu.islandutils.api.music.resources.handler.DownloadHandler +import dev.asodesu.islandutils.api.ui.ProgressBarWidget +import dev.asodesu.islandutils.api.ui.background +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.Button +import net.minecraft.client.gui.components.StringWidget +import net.minecraft.client.gui.layouts.FrameLayout +import net.minecraft.client.gui.layouts.LayoutSettings +import net.minecraft.client.gui.layouts.LinearLayout +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component + +class DownloadScreen(val download: DownloadHandler, val parent: Screen?) : Screen(Component.literal("Downloading")) { + var progressBar: ProgressBarWidget? = null + var text: StringWidget? = null + + override fun init() { + val frame = FrameLayout().apply { + addRenderableOnly(background(spacing = 7, opacity = .5f)) + addChild(LinearLayout.vertical().spacing(5).apply { + addChild(FrameLayout().setMinWidth(300).apply { + addChild(StringWidget(Component.literal("Downloading Assets"), font)) { it.alignHorizontallyLeft().alignVerticallyMiddle() } + addChild(Button.builder(Component.literal("X")){ + download.cancel() + }.width(20).build(), LayoutSettings::alignHorizontallyRight) + }) + + progressBar = addChild(ProgressBarWidget(width = 300, height = 10)) + text = addChild(StringWidget(300, 8, Component.empty(), font)) + }) + } + + frame.arrangeElements() + FrameLayout.centerInRectangle(frame, this.rectangle) + frame.visitWidgets { this.addRenderableWidget(it) } + } + + override fun render(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + progressBar?.progress(download.progress) + text?.message = download.state + if (download.finished) + minecraft?.submit { minecraft?.setScreen(parent) } + + super.render(guiGraphics, i, j, f) + } + + override fun shouldCloseOnEsc() = false +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/IslandUtilsPackResources.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/IslandUtilsPackResources.kt new file mode 100644 index 00000000..2e243c92 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/IslandUtilsPackResources.kt @@ -0,0 +1,25 @@ +package dev.asodesu.islandutils.api.music.resources + +import java.util.* +import net.minecraft.network.chat.Component +import net.minecraft.resources.Identifier +import net.minecraft.server.packs.PackLocationInfo +import net.minecraft.server.packs.PackResources +import net.minecraft.server.packs.PackType +import net.minecraft.server.packs.metadata.MetadataSectionType +import net.minecraft.server.packs.repository.PackSource + +class IslandUtilsPackResources : PackResources { + override fun getRootResource(vararg strings: String) = null + override fun getResource(packType: PackType, identifier: Identifier) = null + override fun listResources(packType: PackType, string: String, string2: String, resourceOutput: PackResources.ResourceOutput) {} + override fun getNamespaces(packType: PackType) = emptySet() + override fun getMetadataSection(metadataSectionType: MetadataSectionType) = null + override fun location() = PackLocationInfo( + "island_utils_injector", + Component.literal("IslandUtils Injected Sounds"), + PackSource.BUILT_IN, + Optional.empty() + ) + override fun close() {} +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/RemoteResources.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/RemoteResources.kt new file mode 100644 index 00000000..3554c5d4 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/RemoteResources.kt @@ -0,0 +1,72 @@ +package dev.asodesu.islandutils.api.music.resources + +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.islandUtilsFolder +import dev.asodesu.islandutils.api.music.resources.handler.DownloadHandler +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.FileOutputStream +import java.io.IOException +import java.net.URI +import net.minecraft.util.FileUtil +import kotlin.io.path.deleteIfExists +import kotlin.io.path.exists + +object RemoteResources { + private val assetsBaseUrl = "https://raw.githubusercontent.com/AsoDesu/islandutils-assets/2.0.0/assets/" + private val assetsFolder = islandUtilsFolder.resolve("assets") + + fun downloaded(asset: String) = file(asset).exists() + + fun use(asset: String) { + SoundInjector.inject(key(asset), file(asset)) + } + + fun delete(asset: String) { + file(asset).deleteIfExists() + } + + fun download(asset: String, progressListener: DownloadHandler? = null) { + val outputFile = file(asset) + try { + FileUtil.createDirectoriesSafe(outputFile.parent) + } catch (e: Exception) { + throw IOException("Failed to create assets directory", e) + } + + var input: BufferedInputStream? = null + var output: BufferedOutputStream? = null + try { + val url = URI("$assetsBaseUrl$asset.ogg").toURL() + val http = url.openConnection() + + val totalLength = http.contentLengthLong + var bytesDownloaded = 0L + + input = BufferedInputStream(http.getInputStream()) + output = BufferedOutputStream(FileOutputStream(outputFile.toFile()), 1024) + val data = ByteArray(1024) + + var x: Int + while (input.read(data, 0, 1024).also { x = it } >= 0) { + bytesDownloaded += x + progressListener?.progress(asset, bytesDownloaded / totalLength.toDouble()) + output.write(data, 0, x) + + if (Thread.interrupted()) throw InterruptedException() + } + progressListener?.done(asset) + input.close() + output.close() + } catch (e: Exception){ + progressListener?.fail(asset, e) + input?.close() + output?.close() + throw e + } + } + + fun key(asset: String) = Resources.islandUtils(asset.replace("/", ".")) + fun file(asset: String) = assetsFolder.resolve("$asset.ogg") + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/SoundInjector.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/SoundInjector.kt new file mode 100644 index 00000000..d37a4a4c --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/SoundInjector.kt @@ -0,0 +1,50 @@ +package dev.asodesu.islandutils.api.music.resources + +import dev.asodesu.islandutils.api.extentions.minecraft +import java.nio.file.Path +import net.minecraft.client.resources.sounds.Sound +import net.minecraft.client.sounds.WeighedSoundEvents +import net.minecraft.resources.Identifier +import net.minecraft.server.packs.resources.IoSupplier +import net.minecraft.server.packs.resources.Resource +import net.minecraft.util.valueproviders.ConstantFloat + +object SoundInjector { + private val registryOverrides = mutableMapOf() + private val soundCacheOverrides = mutableMapOf() + private val islandUtilsPackResources = IslandUtilsPackResources() + + fun inject(location: Identifier, path: Path) { + val fileLocation = Identifier.fromNamespaceAndPath(location.namespace, location.path + "_file") + val soundFileLocation = Sound.SOUND_LISTER.idToFile(fileLocation) + + val resource = Resource(islandUtilsPackResources, IoSupplier.create(path)) + + val soundEvents = WeighedSoundEvents(location, "") + soundEvents.addSound( + Sound( + fileLocation, // sound file location + ConstantFloat.of(1f), // volume + ConstantFloat.of(1f), // pitch + 1, // weight + Sound.Type.FILE, // sound type (unused) + true, // stream + false, // preload + 16 // attenuation distance + ) + ) + registryOverrides[location] = soundEvents + soundCacheOverrides[soundFileLocation] = resource + + val soundManager = minecraft.soundManager ?: return + soundManager.registry[location] = soundEvents + soundManager.soundCache[soundFileLocation] = resource + } + + fun apply() { + val soundManager = minecraft.soundManager + soundManager.registry.putAll(registryOverrides) + soundManager.soundCache.putAll(soundCacheOverrides) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/DownloadHandler.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/DownloadHandler.kt new file mode 100644 index 00000000..535a70f1 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/DownloadHandler.kt @@ -0,0 +1,34 @@ +package dev.asodesu.islandutils.api.music.resources.handler + +import net.minecraft.network.chat.Component +import org.slf4j.LoggerFactory + +abstract class DownloadHandler { + protected val logger = LoggerFactory.getLogger("IU-Download") + protected var thread: Thread? = null + var job: DownloadJob? = null + + open var progress: Double = 0.0 + open var state: Component = Component.empty() + open var finished: Boolean = false + + abstract fun run() + + abstract fun progress(asset: String, progress: Double) + fun fail(asset: String, e: Exception) { + logger.error("Failed to download '$asset'", e) + } + open fun done(asset: String) { + logger.info("Downloaded asset '$asset'!") + } + + open fun cancel() { + thread?.interrupt() + finally() + } + + fun finally() { + finished = true + job?.finish() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/DownloadJob.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/DownloadJob.kt new file mode 100644 index 00000000..939705cf --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/DownloadJob.kt @@ -0,0 +1,31 @@ +package dev.asodesu.islandutils.api.music.resources.handler + +class DownloadJob(val factory: () -> DownloadHandler) { + var currentDownload: DownloadHandler? = null + private set + + fun start() { + if (currentDownload != null) return + factory().also { handler -> + if (handler.finished) return + currentDownload = handler + handler.job = this + } + } + + fun restart() { + currentDownload?.cancel() + currentDownload = null + start() + } + + fun finish() { + currentDownload = null + } + + companion object { + fun single(asset: String) = DownloadJob { SingleDownloadHandler(asset) } + fun multi(assets: Collection) = DownloadJob { MultiDownloadHandler(assets) } + fun multi(vararg assets: String) = DownloadJob { MultiDownloadHandler(assets.toList()) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/MultiDownloadHandler.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/MultiDownloadHandler.kt new file mode 100644 index 00000000..e83a5e42 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/MultiDownloadHandler.kt @@ -0,0 +1,44 @@ +package dev.asodesu.islandutils.api.music.resources.handler + +import dev.asodesu.islandutils.api.music.resources.RemoteResources +import net.minecraft.network.chat.Component + +class MultiDownloadHandler(assets: Collection) : DownloadHandler() { + private val downloadQueue = mutableListOf() + private var i = 0 + + init { + assets.forEach { + RemoteResources.use(it) + if (!RemoteResources.downloaded(it)) downloadQueue += it + } + if (downloadQueue.isNotEmpty()) { + thread = Thread(::run).also { it.start() } + } else { + finally() + } + } + + override fun run() { + for (asset in downloadQueue) { + i++ + try { + RemoteResources.download(asset, this) + } catch (e: Exception) { + if (e is InterruptedException) { + logger.error("Download of $asset interrupted, cancelling.") + RemoteResources.delete(asset) + break + } + } + } + finally() + } + + override fun progress(asset: String, progress: Double) { + val startingProgress = i / downloadQueue.size.toDouble() + val scaledProgress = progress / downloadQueue.size + this.progress = startingProgress + scaledProgress + this.state = Component.literal("($i/${downloadQueue.size}) $asset") + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/SingleDownloadHandler.kt b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/SingleDownloadHandler.kt new file mode 100644 index 00000000..51aefef9 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/music/resources/handler/SingleDownloadHandler.kt @@ -0,0 +1,30 @@ +package dev.asodesu.islandutils.api.music.resources.handler + +import dev.asodesu.islandutils.api.music.resources.RemoteResources +import net.minecraft.network.chat.Component + +class SingleDownloadHandler(val asset: String) : DownloadHandler() { + override var progress: Double = 0.0 + override var state: Component = Component.literal("(1/1) $asset") + + init { + RemoteResources.use(asset) + if (!RemoteResources.downloaded(asset)) { + thread = Thread(::run).also { it.start() } + } else { + finally() + } + } + + override fun run() { + try { + RemoteResources.download(asset, this) + } catch (e: Exception) { + } + this.finally() + } + + override fun progress(asset: String, progress: Double) { + this.progress = progress + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/notifier/Notification.kt b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/Notification.kt new file mode 100644 index 00000000..89c4e25f --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/Notification.kt @@ -0,0 +1,5 @@ +package dev.asodesu.islandutils.api.notifier + +import net.minecraft.network.chat.Component + +class Notification(val message: Component, val source: NotificationSource) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/notifier/NotificationSource.kt b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/NotificationSource.kt new file mode 100644 index 00000000..e1dd4c51 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/NotificationSource.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.notifier + +import net.minecraft.network.chat.MutableComponent + +interface NotificationSource { + val title: MutableComponent + fun provide(): List +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/notifier/Notifier.kt b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/Notifier.kt new file mode 100644 index 00000000..0a21cbf8 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/Notifier.kt @@ -0,0 +1,57 @@ +package dev.asodesu.islandutils.api.notifier + +import dev.asodesu.islandutils.api.extentions.buildComponent +import dev.asodesu.islandutils.api.extentions.style +import net.minecraft.network.chat.Component + +object Notifier { + private val TOOLTIP_TITLE_COMPONENT = Component.translatable("islandutils.notifications.title").style { withUnderlined(true) } + private val TOOLTIP_GROUP_PADDING = Component.literal(" ") + + private var tooltip: List = emptyList() + private val sources = mutableSetOf() + + private var ticks = 0 + // called from mixin every tick while multiplayer join screen is open + fun tickActive() { + // only run every second + if ((ticks++ % 20) != 0) return + + val notifications = sources.flatMap { it.provide() } + if (notifications.isEmpty()) { + tooltip = emptyList() + return + } + + val groupedNotifications = notifications.groupBy { it.source } + tooltip = buildList { + add(TOOLTIP_TITLE_COMPONENT) + add(Component.empty()) + groupedNotifications.forEach { (source, notifs) -> + add(source.title) + notifs.forEach { notification -> + // add group badding + add(buildComponent { + append(TOOLTIP_GROUP_PADDING) + append(notification.message) + }) + } + } + } + } + + // called from mixin when multiplayer join screen opens + fun activate() { + ticks = 0 + tickActive() + } + + fun add(source: NotificationSource) { + source.title.withStyle { it.withBold(true) } + sources.add(source) + } + + fun isEmpty() = tooltip.isEmpty() + fun get() = tooltip + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/notifier/ServerListNotificationRenderer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/ServerListNotificationRenderer.kt new file mode 100644 index 00000000..2cd91e6a --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/notifier/ServerListNotificationRenderer.kt @@ -0,0 +1,35 @@ +package dev.asodesu.islandutils.api.notifier + +import dev.asodesu.islandutils.api.extentions.isInsideBox +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.server.ServerSessionHandler +import dev.asodesu.islandutils.options.NotificationOptions +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList.OnlineServerEntry +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.resources.Identifier + +object ServerListNotificationRenderer { + private val enabled by NotificationOptions.showInServerMenu + private val NOTIFICATION_SPRITE = Identifier.withDefaultNamespace("icon/unseen_notification") + private val SERVER_ICON_SIZE = 32 // size of the server icon in minecraft + + private val ICON_SIZE = 10 + private val ICON_PADDING_X = 5 + + fun render(guiGraphics: GuiGraphics, entry: OnlineServerEntry, y: Int, x: Int, mouseX: Int, mouseY: Int) { + if (!enabled || !ServerSessionHandler.isIslandServer(entry.serverData)) return + if (Notifier.isEmpty()) return + + val iconX = x - ICON_SIZE - ICON_PADDING_X + val iconY = y + ((SERVER_ICON_SIZE - ICON_SIZE) / 2) + + // check if we're mousing over the notification + if (isInsideBox(mouseX, mouseY, iconX, iconY, ICON_SIZE, ICON_SIZE)) { + guiGraphics.setComponentTooltipForNextFrame(minecraft.font, Notifier.get(), mouseX, mouseY) + } + + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, NOTIFICATION_SPRITE, iconX, iconY, ICON_SIZE, ICON_SIZE) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/Config.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/Config.kt new file mode 100644 index 00000000..3b0bfb87 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/Config.kt @@ -0,0 +1,53 @@ +package dev.asodesu.islandutils.api.options + +import dev.asodesu.islandutils.api.json.decode +import dev.asodesu.islandutils.api.options.screen.ConfigScreen +import java.io.File +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import net.minecraft.client.gui.screens.Screen +import org.slf4j.LoggerFactory + +abstract class Config(val file: File, val fileVersion: String = "2.0.0") { + private val logger = LoggerFactory.getLogger("IU-Options") + protected abstract val entries: List + + open fun getScreen(parent: Screen) = ConfigScreen(parent, this, entries) + + fun load() { + if (!file.exists()) { + save() + return logger.error("${file.name} file does not exist, not loading...") + } + logger.info("Loading ${file.name} config...") + + val json = try { + file.readText().decode() + } catch (e: Exception) { + logger.error("Failed to load config file", e) + return + } + + val versionString = (json["_version"] as? JsonPrimitive)?.content + if (versionString == null || versionString != fileVersion) { + // TODO: DFU + logger.info("Config version '$versionString' differs from expected version '$fileVersion'") + } + + this.entries.forEach { it.load(json) } + logger.info("Loaded config ${file.name}!") + } + + fun save() { + val obj = buildJsonObject { + put("_", "DO NOT EDIT THIS FILE UNLESS YOU KNOW WHAT YOU ARE DOING") + put("_version", fileVersion) + entries.forEach { it.save(this) } + } + + file.writeText(obj.toString()) + logger.info("Saved config!") + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigEntry.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigEntry.kt new file mode 100644 index 00000000..27724249 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigEntry.kt @@ -0,0 +1,13 @@ +package dev.asodesu.islandutils.api.options + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.gui.layouts.LayoutElement + +interface ConfigEntry { + fun load(json: JsonObject) + fun save(json: JsonObjectBuilder) + + fun render(layout: Layout): LayoutElement +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigExt.kt new file mode 100644 index 00000000..7d57d58b --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigExt.kt @@ -0,0 +1,19 @@ +package dev.asodesu.islandutils.api.options + +import dev.asodesu.islandutils.api.options.option.Option + +fun Option.onEnable(func: () -> Unit) = apply { + this.onChange { last, new -> + if (last != new && new) func() + } +} + +fun Option.onDisabled(func: () -> Unit) = apply { + this.onChange { last, new -> + if (last != new && !new) func() + } +} + +fun Option.onChange(func: () -> Unit) = apply { + this.onChange { last, new -> func() } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigGroup.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigGroup.kt new file mode 100644 index 00000000..30b8b02d --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigGroup.kt @@ -0,0 +1,41 @@ +package dev.asodesu.islandutils.api.options + +import dev.asodesu.islandutils.api.options.option.Option +import dev.asodesu.islandutils.api.options.option.OptionRenderer +import dev.asodesu.islandutils.api.options.option.ToggleOptionRenderer +import dev.asodesu.islandutils.api.options.screen.tab.ConfigGroupLayout +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.gui.layouts.LayoutElement +import net.minecraft.network.chat.Component + +abstract class ConfigGroup(val name: String) : ConfigEntry { + val component: Component = Component.translatable("islandutils.options.$name") + private val children = mutableListOf() + + protected fun option(name: String, def: T, serializer: KSerializer, renderer: OptionRenderer, desc: Boolean = false): Option { + return Option(name, def, serializer, renderer, desc) + .also { children += it } + } + + protected fun toggle(name: String, def: Boolean, desc: Boolean = false, renderer: OptionRenderer = ToggleOptionRenderer) + = option(name, def, Boolean.serializer(), renderer, desc) + + protected fun group(group: ConfigGroup) { + children += group + } + + override fun render(layout: Layout): LayoutElement = ConfigGroupLayout(this) + fun children() = children as List + + override fun load(json: JsonObject) { + children.forEach { it.load(json) } + } + + override fun save(json: JsonObjectBuilder) { + children.forEach { it.save(json) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigSection.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigSection.kt new file mode 100644 index 00000000..05616937 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/ConfigSection.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.options + +import dev.asodesu.islandutils.api.options.screen.tab.SectionScreenTab +import net.minecraft.client.gui.layouts.Layout + +abstract class ConfigSection(name: String) : ConfigGroup(name) { + override fun render(layout: Layout) = SectionScreenTab(this) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/download/DownloadExt.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/download/DownloadExt.kt new file mode 100644 index 00000000..ca696a02 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/download/DownloadExt.kt @@ -0,0 +1,12 @@ +package dev.asodesu.islandutils.api.options.download + +import dev.asodesu.islandutils.api.music.resources.handler.DownloadJob +import dev.asodesu.islandutils.api.options.onEnable +import dev.asodesu.islandutils.api.options.option.Option + +fun Option.withDownloadJob(job: DownloadJob, startTrigger: (() -> Unit) -> Any) = apply { + startTrigger { job.start() } + this.withRenderer(DownloaderOptionRenderer(this.renderer(), job)) +} + +fun Option.withDownloadJob(job: DownloadJob) = withDownloadJob(job, this::onEnable) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/download/DownloaderOptionRenderer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/download/DownloaderOptionRenderer.kt new file mode 100644 index 00000000..d4d3b362 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/download/DownloaderOptionRenderer.kt @@ -0,0 +1,114 @@ +package dev.asodesu.islandutils.api.options.download + +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.extentions.rect +import dev.asodesu.islandutils.api.extentions.scissor +import dev.asodesu.islandutils.api.music.resources.handler.DownloadJob +import dev.asodesu.islandutils.api.options.option.Option +import dev.asodesu.islandutils.api.options.option.OptionRenderer +import dev.asodesu.islandutils.api.ui.ProgressBarWidget +import dev.asodesu.islandutils.api.ui.tween.Easing +import dev.asodesu.islandutils.api.ui.tween.Tween +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.AbstractWidget +import net.minecraft.client.gui.components.StringWidget +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.gui.layouts.LayoutElement +import net.minecraft.client.gui.layouts.LinearLayout +import net.minecraft.client.gui.narration.NarrationElementOutput +import net.minecraft.network.chat.Component +import net.minecraft.util.ARGB +import kotlin.time.Duration.Companion.seconds + +class DownloaderOptionRenderer(private val renderer: OptionRenderer, private val job: DownloadJob) : OptionRenderer { + + override fun render(option: Option, layout: Layout): LayoutElement { + return LinearLayout.vertical().apply { + addChild(renderer.render(option, layout)) + addChild(DownloadSectionRenderer(job, layout)) + } + } + + class DownloadSectionRenderer(val job: DownloadJob, val parentLayout: Layout) : AbstractWidget(0, 0, 150, 0, Component.empty()) { + private val PADDING = 8 + private val TWEEN_DURATION = 0.2.seconds + + private var didHaveJob = false + private var heightTween: Tween? = null + + private val widgets = mutableListOf() + private val layout: Layout + private val stringWidget: StringWidget + private val progressBar: ProgressBarWidget + + init { + layout = LinearLayout.vertical().spacing(3).apply { + stringWidget = addChild(StringWidget(150, 8, Component.empty(), minecraft.font)) + progressBar = addChild(ProgressBarWidget(height = 8, backgroundColor = ARGB.color(255, 25, 30, 51))) + } + arrangeLayout() + height = targetHeight() + active = false + layout.visitWidgets { widgets += it } + } + + override fun renderWidget(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + tickTween(f) + + val currentDownload = job.currentDownload + if (currentDownload == null) { + if (didHaveJob) { + heightTween = Tween.int(height, targetHeight(), TWEEN_DURATION, easing = Easing.EASE_OUT_EXPO) + didHaveJob = false + } + return + } else if (!didHaveJob) { + heightTween = Tween.int(height, targetHeight(), TWEEN_DURATION, easing = Easing.EASE_OUT_EXPO) + didHaveJob = true + } + + guiGraphics.scissor(x, y, width, height) { + guiGraphics.rect(x, y, width, height, ARGB.color(45, 54, 91)) + + stringWidget.message = currentDownload.state + progressBar.progress(currentDownload.progress) + + widgets.forEach { it.render(guiGraphics, i, j, f) } + } + this.setTooltip(null) + } + + private fun tickTween(tickDeta: Float) { + val tween = heightTween ?: return + height = tween.tick(tickDeta) + parentLayout.arrangeElements() + if (tween.finished) heightTween = null + } + + private fun targetHeight() = if (job.currentDownload != null) (layout.height + PADDING * 2) else 0 + + private fun arrangeLayout() { + layout.x = x + PADDING + layout.y = y + PADDING + layout.arrangeElements() + } + + override fun setX(i: Int) { + super.setX(i) + arrangeLayout() + } + override fun setY(i: Int) { + super.setY(i) + arrangeLayout() + } + override fun setWidth(i: Int) { + super.setWidth(i) + layout.visitWidgets { it.width = (i - PADDING * 2) } + } + + override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) { + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/option/Option.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/option/Option.kt new file mode 100644 index 00000000..a6e377de --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/option/Option.kt @@ -0,0 +1,76 @@ +package dev.asodesu.islandutils.api.options.option + +import dev.asodesu.islandutils.api.extentions.appendLine +import dev.asodesu.islandutils.api.extentions.buildComponent +import dev.asodesu.islandutils.api.extentions.copyAndStyle +import dev.asodesu.islandutils.api.extentions.newLine +import dev.asodesu.islandutils.api.options.ConfigEntry +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.components.AbstractWidget +import net.minecraft.client.gui.components.Tooltip +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.network.chat.Component +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class Option( + private val name: String, + private val default: T, + private val serializer: KSerializer, + private var renderer: OptionRenderer, + private val hasDescription: Boolean +) : ReadOnlyProperty, ConfigEntry { + val component = Component.translatable("islandutils.options.$name") + private val descriptionComponent = Component.translatable("islandutils.options.$name.desc") + .withStyle(ChatFormatting.DARK_AQUA) + private var value = default + private var onChange: MutableList<((T, T) -> Unit)> = mutableListOf() + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return value + } + + fun onChange(func: (T, T) -> Unit) = apply { + this.onChange += func + } + fun withRenderer(renderer: OptionRenderer) = apply { + this.renderer = renderer + } + + fun get() = value + fun set(newValue: T) { + val oldValue = this.value + this.value = newValue + this.onChange.forEach { it(oldValue, newValue) } + } + + fun renderer() = renderer + override fun render(layout: Layout) = renderer.render(this, layout).also { widget -> + + val tooltip = Tooltip.create(buildComponent { + appendLine(component.copyAndStyle { withBold(true) }) + newLine() + append(descriptionComponent) + }) + if (widget is AbstractWidget) { + widget.setTooltip(tooltip) + } else if (widget is Layout) { + widget.visitWidgets { it.setTooltip(tooltip) } + } + } + + override fun load(json: JsonObject) { + val element = json[name] ?: return + val obj = Json.decodeFromJsonElement(serializer, element) + this.value = obj + } + + override fun save(json: JsonObjectBuilder) { + val element = Json.encodeToJsonElement(serializer, value) + json.put(name, element) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/option/OptionRenderer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/option/OptionRenderer.kt new file mode 100644 index 00000000..b8940b08 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/option/OptionRenderer.kt @@ -0,0 +1,8 @@ +package dev.asodesu.islandutils.api.options.option + +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.gui.layouts.LayoutElement + +interface OptionRenderer { + fun render(option: Option, layout: Layout): LayoutElement +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/option/ToggleOptionRenderer.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/option/ToggleOptionRenderer.kt new file mode 100644 index 00000000..34161bdd --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/option/ToggleOptionRenderer.kt @@ -0,0 +1,33 @@ +package dev.asodesu.islandutils.api.options.option + +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.ui.FlatButton +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.gui.layouts.LayoutElement +import net.minecraft.client.renderer.RenderPipelines + +object ToggleOptionRenderer : OptionRenderer { + override fun render(option: Option, layout: Layout): LayoutElement { + return ToggleButton(option) + } + + class ToggleButton(val option: Option) : FlatButton(option.component) { + val TOGGLE_ON = Resources.islandUtils("widget/toggle/on") + val TOGGLE_OFF = Resources.islandUtils("widget/toggle/off") + val TOGGLE_WIDTH = 24 + val TOGGLE_HEIGHT = 12 + + override fun renderWidget0(guiGraphics: GuiGraphics, f: Float) { + val sprite = if (option.get()) TOGGLE_ON else TOGGLE_OFF + + val toggleX = x + this.width - TOGGLE_WIDTH - PADDING + val toggleY = y + (this.height - TOGGLE_HEIGHT) / 2 + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, toggleX, toggleY, TOGGLE_WIDTH, TOGGLE_HEIGHT) + } + + override fun onPress() { + option.set(!option.get()) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/ConfigScreen.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/ConfigScreen.kt new file mode 100644 index 00000000..63152664 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/ConfigScreen.kt @@ -0,0 +1,171 @@ +package dev.asodesu.islandutils.api.options.screen + +import dev.asodesu.islandutils.api.options.Config +import dev.asodesu.islandutils.api.options.ConfigEntry +import dev.asodesu.islandutils.api.options.ConfigSection +import dev.asodesu.islandutils.api.options.screen.tab.ConfigScreenTab +import dev.asodesu.islandutils.api.ui.BackgroundWidget +import dev.asodesu.islandutils.api.ui.FlatButton +import dev.asodesu.islandutils.api.ui.background +import java.util.concurrent.CompletableFuture +import net.minecraft.client.gui.components.Button +import net.minecraft.client.gui.components.StringWidget +import net.minecraft.client.gui.layouts.FrameLayout +import net.minecraft.client.gui.layouts.LayoutSettings +import net.minecraft.client.gui.layouts.LinearLayout +import net.minecraft.client.gui.layouts.SpacerElement +import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.input.MouseButtonEvent +import net.minecraft.network.chat.Component +import kotlin.math.min + +class ConfigScreen( + private val parent: Screen, + private val config: Config, + private val entries: List +) : Screen(Component.literal("IslandUtils Options")) { + // consts + val BACKGROUND_OPACITY = .5f + val TAB_BACKGROUND_OPACITY = .75f + val TAB_BUTTONS_PADDING = 3 + val CONTAINER_PADDING = 7 + val HEIGHT = 300 + + lateinit var screenLayout: LinearLayout + lateinit var tabsFrame: FrameLayout + lateinit var configFrame: FrameLayout + + val tabButtons = mutableMapOf() + var tab: ConfigScreenTab? = null + + override fun init() { + var firstTab: ConfigSection? = null + tabsFrame = FrameLayout().apply { + addRenderableOnly(background(spacing = CONTAINER_PADDING, opacity = TAB_BACKGROUND_OPACITY, sprite = BackgroundWidget.LEFT)) + + // tabs button + addChild(LinearLayout.vertical().spacing(TAB_BUTTONS_PADDING).apply { + addChild(SpacerElement.height(-2)) // jank :3 + addChild(StringWidget(this@ConfigScreen.title, minecraft!!.font)) + addChild(SpacerElement.height(0)) // height of 0 since this adds to the padding + + entries.forEach { entry -> + if (entry !is ConfigSection) return@forEach + if (firstTab == null) firstTab = entry + + val translatable = Component.translatable("islandutils.options.${entry.name}") + val tabButton = Button.builder(translatable) { _ -> switchTab(entry) }.build() + addChild(tabButton) + + tabButtons[entry] = tabButton + } + }, LayoutSettings::alignVerticallyTop) + + addChild(LinearLayout.vertical().apply { + val doneComponent = Component.translatable("gui.done") + addChild(Button.builder(doneComponent) { close() }.build(), LayoutSettings::alignVerticallyBottom) + }, LayoutSettings::alignVerticallyBottom) + } + + configFrame = FrameLayout().apply { + addRenderableOnly(background(spacing = CONTAINER_PADDING, opacity = BACKGROUND_OPACITY, sprite = BackgroundWidget.RIGHT)) + } + + screenLayout = LinearLayout.horizontal().spacing(CONTAINER_PADDING * 2).apply { + addChild(tabsFrame) + addChild(configFrame) + visitWidgets { this@ConfigScreen.addRenderableWidget(it) } + } + + firstTab?.let { tab -> switchTab(tab) } + this.repositionElements() + } + + override fun repositionElements() { + tabsFrame.arrangeElements() + val minHeight = minHeight() + val minWidth = minWidth() + + tabsFrame.setMinHeight(minHeight) + configFrame.setMinHeight(minHeight) + configFrame.setMinWidth(minWidth) + + screenLayout.arrangeElements() + FrameLayout.centerInRectangle(screenLayout, this.rectangle) + + tab?.let { tab -> + val tabLayout = tab.layout + tabLayout.x = configFrame.x + tabLayout.y = configFrame.y + tab.setMinWidth(minWidth) + tabLayout.arrangeElements() + } + } + + fun minHeight() = min(HEIGHT, this.height - 25) + fun minWidth() = if (this.width >= (400 + tabsFrame.width)) 400 else this.width - tabsFrame.width - 50 + + private fun switchTab(section: ConfigSection) { + if (tab != null) { + tab!!.layout.visitWidgets { this.removeWidget(it) } + tab!!.close() + } + + tab = section.render(configFrame).apply { + init() + x = configFrame.x + y = configFrame.y + setMinWidth(minWidth()) + this.layout.arrangeElements() + visitWidgets { addRenderableWidget(it) } + } + + tabButtons.forEach { (buttonSection, button) -> + button.isFocused = buttonSection == section + } + } + + override fun removed() { + tab?.close() + CompletableFuture.runAsync { config.save() } + } + + fun close() { + minecraft?.setScreen(parent) + } + + var lastClicked: FlatButton? = null + override fun mouseClicked(mouseButtonEvent: MouseButtonEvent, bl: Boolean): Boolean { + // taken from super method + + // this fixes an annoying bug? in minecraft where the buttons + // remain focussed after you click them, which makes our flat + // buttons look REAL bad + + val optional = this.getChildAt(mouseButtonEvent.x, mouseButtonEvent.y) + if (optional.isEmpty) { + return false + } else { + val guiEventListener = optional.get() + if (guiEventListener.mouseClicked(mouseButtonEvent, bl)) { + if (guiEventListener !is FlatButton) this.focused = guiEventListener + else { + this.lastClicked = guiEventListener + this.focused = null + } + if (mouseButtonEvent.isDown) this.isDragging = true + } + return true + } + } + + override fun mouseReleased(mouseButtonEvent: MouseButtonEvent): Boolean { + val handle = super.mouseReleased(mouseButtonEvent) + lastClicked?.let { + lastClicked = null + return it.mouseReleased(mouseButtonEvent) + } + return handle + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/ConfigGroupLayout.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/ConfigGroupLayout.kt new file mode 100644 index 00000000..732cefc6 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/ConfigGroupLayout.kt @@ -0,0 +1,20 @@ +package dev.asodesu.islandutils.api.options.screen.tab + +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.options.ConfigGroup +import dev.asodesu.islandutils.api.ui.LayoutDelegate +import net.minecraft.client.gui.components.StringWidget +import net.minecraft.client.gui.layouts.LinearLayout +import net.minecraft.client.gui.layouts.SpacerElement + +class ConfigGroupLayout(private val group: ConfigGroup) : LayoutDelegate() { + override val layout: LinearLayout = LinearLayout.vertical().spacing(3).apply { + addChild(SpacerElement.height(2)) + addChild(StringWidget(group.component, minecraft.font)) + addChild(LinearLayout.vertical().spacing(3).apply { + group.children().forEach { + addChild(it.render(this)) + } + }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/ConfigScreenTab.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/ConfigScreenTab.kt new file mode 100644 index 00000000..df3c23e1 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/ConfigScreenTab.kt @@ -0,0 +1,28 @@ +package dev.asodesu.islandutils.api.options.screen.tab + +import java.util.function.Consumer +import net.minecraft.client.gui.components.AbstractWidget +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.gui.layouts.LayoutElement +import net.minecraft.client.gui.navigation.ScreenRectangle + +interface ConfigScreenTab : LayoutElement { + + val layout: Layout + + fun setMinWidth(width: Int) + + fun init() + fun close() + + // why can't kotlin delegates do this in interfaces >:( + override fun setX(i: Int) { layout.x = i } + override fun setY(i: Int) { layout.y = i } + override fun getX() = layout.x + override fun getY() = layout.y + override fun getWidth() = layout.width + override fun getHeight() = layout.height + override fun visitWidgets(consumer: Consumer) = layout.visitWidgets(consumer) + override fun getRectangle(): ScreenRectangle = layout.rectangle + override fun setPosition(i: Int, j: Int) = layout.setPosition(i, j) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/SectionScreenTab.kt b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/SectionScreenTab.kt new file mode 100644 index 00000000..d8cc0ff3 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/options/screen/tab/SectionScreenTab.kt @@ -0,0 +1,29 @@ +package dev.asodesu.islandutils.api.options.screen.tab + +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.options.ConfigSection +import net.minecraft.client.gui.components.StringWidget +import net.minecraft.client.gui.layouts.LinearLayout +import net.minecraft.client.gui.layouts.SpacerElement +import net.minecraft.network.chat.Component + +class SectionScreenTab(private val section: ConfigSection) : ConfigScreenTab { + override val layout: LinearLayout = LinearLayout.vertical().spacing(3).apply { + addChild(StringWidget(Component.translatable("islandutils.options.${section.name}"), minecraft.font)) + addChild(SpacerElement.height(0)) + + section.children().forEach { + addChild(it.render(this)) + } + } + + override fun setMinWidth(width: Int) { + layout.visitWidgets { it.width = width } + } + + override fun init() { + } + + override fun close() { + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/server/InstanceJoinCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/server/InstanceJoinCallback.kt new file mode 100644 index 00000000..297bcf8b --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/server/InstanceJoinCallback.kt @@ -0,0 +1,5 @@ +package dev.asodesu.islandutils.api.server + +fun interface InstanceJoinCallback { + fun onInstanceSwitch() +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerDisconnectCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerDisconnectCallback.kt new file mode 100644 index 00000000..44bb2767 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerDisconnectCallback.kt @@ -0,0 +1,5 @@ +package dev.asodesu.islandutils.api.server + +fun interface ServerDisconnectCallback { + fun onServerDisconnect() +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerEvents.kt b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerEvents.kt new file mode 100644 index 00000000..a9f2cd8f --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerEvents.kt @@ -0,0 +1,23 @@ +package dev.asodesu.islandutils.api.server + +import dev.asodesu.islandutils.api.events.arrayBackedEvent + +object ServerEvents { + val SERVER_JOIN = arrayBackedEvent { callbacks -> + ServerJoinCallback { + callbacks.forEach { it.onServerConnect() } + } + } + + val INSTANCE_JOIN = arrayBackedEvent { callbacks -> + InstanceJoinCallback { + callbacks.forEach { it.onInstanceSwitch() } + } + } + + val SERVER_DISCONNECT = arrayBackedEvent { callbacks -> + ServerDisconnectCallback { + callbacks.forEach { it.onServerDisconnect() } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerJoinCallback.kt b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerJoinCallback.kt new file mode 100644 index 00000000..39840c1a --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerJoinCallback.kt @@ -0,0 +1,5 @@ +package dev.asodesu.islandutils.api.server + +fun interface ServerJoinCallback { + fun onServerConnect() +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerSessionHandler.kt b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerSessionHandler.kt new file mode 100644 index 00000000..c2d12a64 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/server/ServerSessionHandler.kt @@ -0,0 +1,69 @@ +package dev.asodesu.islandutils.api.server + +import dev.asodesu.islandutils.IslandUtils +import dev.asodesu.islandutils.api.extentions.calculateSha256 +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.extentions.minecraft +import net.minecraft.client.multiplayer.ServerData +import net.minecraft.network.protocol.game.ClientboundLoginPacket + +object ServerSessionHandler { + private val hostnames = listOf( + "mccisland.net", + "mccisland.com" + ) + private val nonProdIps = listOf( + "e927084bb931f83eece6780afd9046f121a798bf3ff3c78a9399b08c1dfb1aec", // bigrat.mccisland.net + "0c932ffaa687c756c4616a24eb49389213519ea8d18e0d9bdfd2d335771c35c7", + "7f0d15bbb2ffaee1bbf0d23e5746afb753333d590f71ff8a5a186d86c3e79dda", + "09445264a9c515c83fc5a0159bda82e25d70d499f80df4a2d1c2f7e2ae6af997" + ) // oooo top secret noxcrew secrets :3 + + var isOnline = false + var isProduction = true + var joinTime = System.currentTimeMillis() + + // here we run this on the render thread cuz for some reason if you don't do that + // you can't join any minecraft servers :shrug: + // + // i have since discovered that the "debug" function cannot be called on + // the non-render thread since it tries to put things in chat lol + fun onConnect(serverData: ServerData) = minecraft.submit { + if (isIslandServer(serverData)) { + isOnline = true + isProduction = isProductionServer(serverData) + joinTime = System.currentTimeMillis() + debug("Joined a ${if (isProduction) "production" else "non-production"} MCC Island server") + ServerEvents.SERVER_JOIN.invoker().onServerConnect() + } else { + isOnline = false + IslandUtils.logger.info("Joined a minecraft server (not mcc island, features disabled)") + } + } + + fun onInstanceJoin(clientboundLoginPacket: ClientboundLoginPacket) = minecraft.submit { + ServerEvents.INSTANCE_JOIN.invoker().onInstanceSwitch() + } + + // idk if we need to run on the render thread here but we'll do it for good measure + fun onDisconnect() = minecraft.submit { + if (!isOnline) { + IslandUtils.logger.info("Disconnected from a minecraft server (not mcc island, disconnect not triggered)") + return@submit + } + ServerEvents.SERVER_DISCONNECT.invoker().onServerDisconnect() + debug("Disconnected an MCC Island server") + isOnline = false + isProduction = true + } + + fun isIslandServer(serverData: ServerData): Boolean { + val hostname = serverData.ip.lowercase() + return hostnames.any { hostname.contains(it) } + } + + fun isProductionServer(serverData: ServerData): Boolean { + val hostnameHash = serverData.ip.lowercase().calculateSha256() + return !nonProdIps.contains(hostnameHash) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/ui/BackgroundWidget.kt b/src/main/kotlin/dev/asodesu/islandutils/api/ui/BackgroundWidget.kt new file mode 100644 index 00000000..d10fa62c --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/ui/BackgroundWidget.kt @@ -0,0 +1,38 @@ +package dev.asodesu.islandutils.api.ui + +import dev.asodesu.islandutils.api.extentions.Resources +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.Renderable +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.resources.Identifier +import net.minecraft.util.ARGB + +class BackgroundWidget( + private val layout: Layout, + private val spacing: Int, + private val opacity: Float, + val sprite: Identifier = ALL +) : Renderable { + + companion object { + val LEFT = Resources.islandUtils("widget/background/left") + val ALL = Resources.islandUtils("widget/background/all") + val RIGHT = Resources.islandUtils("widget/background/right") + } + + override fun render(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + val x = layout.x - spacing + val y = layout.y - spacing + // add spacing twice to compensate for the spacing we just subtracted + val width = layout.width + spacing + spacing + val height = layout.height + spacing + spacing + + guiGraphics.blitRoundedBox(x, y, width, height, sprite, opacity) + } +} + +fun Layout.background(spacing: Int = 0, opacity: Float, sprite: Identifier = BackgroundWidget.ALL) = BackgroundWidget(this, spacing, opacity, sprite) +fun GuiGraphics.blitRoundedBox(x: Int, y: Int, width: Int, height: Int, sprite: Identifier = BackgroundWidget.ALL, opacity: Float = 1f) { + this.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, x, y, width, height, ARGB.white(opacity)) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/ui/FlatButton.kt b/src/main/kotlin/dev/asodesu/islandutils/api/ui/FlatButton.kt new file mode 100644 index 00000000..d67a8b92 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/ui/FlatButton.kt @@ -0,0 +1,87 @@ +package dev.asodesu.islandutils.api.ui + +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.extentions.rect +import dev.asodesu.islandutils.api.extentions.vecRgb +import dev.asodesu.islandutils.api.ui.tween.Easing +import dev.asodesu.islandutils.api.ui.tween.Tween +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.AbstractWidget +import net.minecraft.client.gui.narration.NarrationElementOutput +import net.minecraft.client.input.KeyEvent +import net.minecraft.client.input.MouseButtonEvent +import net.minecraft.network.chat.Component +import net.minecraft.util.ARGB +import net.minecraft.world.phys.Vec3 +import kotlin.time.Duration.Companion.seconds + +abstract class FlatButton(message: Component, width: Int = 150, height: Int = 20) : AbstractWidget(0, 0, width, height, message) { + protected open val COLORS = mapOf( + ButtonState.NONE to vecRgb(51, 62, 104), + ButtonState.HOVERED to vecRgb(65, 78, 130), + ButtonState.CLICKED to vecRgb(71, 86, 142), + ) + protected open val PADDING = 8 + + var colorTween: Tween? = null + var state = ButtonState.NONE + var isClicked = false + + final override fun renderWidget(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + val newState = buttonState() + if (state != newState) { + colorTween = Tween.vec3(targetColor(state), targetColor(newState), 0.2.seconds, Easing.EASE_OUT_EXPO) + state = newState + } + + val vec = colorTween?.tick(f) ?: targetColor(state) + val color = ARGB.color(vec) + guiGraphics.rect(x, y, width, height, color) + if (isFocused) guiGraphics.renderOutline(x, y, width, height, ARGB.white(1f)) + + val font = minecraft.font + guiGraphics.drawString(font, message, x + PADDING, y + ((this.height - font.lineHeight) / 2) + 1, ARGB.white(1f)) + renderWidget0(guiGraphics, f) + + this.handleCursor(guiGraphics) + } + + fun buttonState() = when { + isClicked -> ButtonState.CLICKED + isHovered -> ButtonState.HOVERED + else -> ButtonState.NONE + } + fun targetColor(state: ButtonState) = COLORS[state]!! + + abstract fun renderWidget0(guiGraphics: GuiGraphics, f: Float) + abstract fun onPress() + + override fun onClick(mouseButtonEvent: MouseButtonEvent, bl: Boolean) { + onPress() + isClicked = true + } + override fun onRelease(mouseButtonEvent: MouseButtonEvent) { + isClicked = false + } + + // appropriated from AbstractButton + override fun keyPressed(keyEvent: KeyEvent): Boolean { + if (!this.active || !this.visible) { + return false + } else if (keyEvent.isSelection) { + this.onPress() + return true + } else { + return false + } + } + override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) { + this.defaultButtonNarrationText(narrationElementOutput) + } + + enum class ButtonState { + NONE, + HOVERED, + CLICKED + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/ui/LayoutDelegate.kt b/src/main/kotlin/dev/asodesu/islandutils/api/ui/LayoutDelegate.kt new file mode 100644 index 00000000..f1087008 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/ui/LayoutDelegate.kt @@ -0,0 +1,22 @@ +package dev.asodesu.islandutils.api.ui + +import java.util.function.Consumer +import net.minecraft.client.gui.components.AbstractWidget +import net.minecraft.client.gui.layouts.Layout +import net.minecraft.client.gui.layouts.LayoutElement + +abstract class LayoutDelegate : Layout { + abstract val layout: Layout + + override fun setX(i: Int) { layout.x = i } + override fun setY(i: Int) { layout.y = i } + override fun getX() = layout.x + override fun getY() = layout.y + override fun getWidth() = layout.width + override fun getHeight() = layout.height + override fun arrangeElements() = layout.arrangeElements() + override fun getRectangle() = layout.rectangle + override fun setPosition(i: Int, j: Int) = layout.setPosition(i, j) + override fun visitWidgets(consumer: Consumer) = layout.visitWidgets(consumer) + override fun visitChildren(consumer: Consumer) = layout.visitChildren(consumer) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/ui/ProgressBarWidget.kt b/src/main/kotlin/dev/asodesu/islandutils/api/ui/ProgressBarWidget.kt new file mode 100644 index 00000000..943f4c71 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/ui/ProgressBarWidget.kt @@ -0,0 +1,34 @@ +package dev.asodesu.islandutils.api.ui + +import dev.asodesu.islandutils.api.extentions.Resources +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.AbstractWidget +import net.minecraft.client.gui.narration.NarrationElementOutput +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.network.chat.Component +import net.minecraft.util.ARGB + +class ProgressBarWidget( + val foregroundColor: Int = ARGB.white(1f), + val backgroundColor: Int = ARGB.color(255, 0, 0, 0), + x: Int = 0, + y: Int = 0, + width: Int = 150, + height: Int = 20 +) : AbstractWidget(x, y, width, height, Component.empty()) { + private val SPRITE = Resources.islandUtils("widget/rounded_1") + private var progress = 0.0 + + override fun renderWidget(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + val progressWidth = this.width * progress + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, SPRITE, this.x, this.y, this.width, this.height, backgroundColor) + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, SPRITE, this.x, this.y, progressWidth.toInt(), this.height, foregroundColor) + } + + fun progress(double: Double) { + this.progress = double + } + + override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) { + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/Easing.kt b/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/Easing.kt new file mode 100644 index 00000000..091e1633 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/Easing.kt @@ -0,0 +1,11 @@ +package dev.asodesu.islandutils.api.ui.tween + +import kotlin.math.pow + +fun interface Easing { + fun transform(x: Float): Float + + companion object { + val EASE_OUT_EXPO = Easing { if (it == 1f) 1f else 1 - 2f.pow(-10f * it) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/Tween.kt b/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/Tween.kt new file mode 100644 index 00000000..0f937037 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/Tween.kt @@ -0,0 +1,35 @@ +package dev.asodesu.islandutils.api.ui.tween + +import dev.asodesu.islandutils.api.extentions.ticks +import net.minecraft.util.Mth +import net.minecraft.world.phys.Vec3 +import kotlin.time.Duration + +class Tween( + private val from: T, + private val to: T, + duration: Duration, + private val interpolator: TweenInterpolator, + private val easing: Easing? = null, +) { + private val durationTicks = duration.ticks + private var elapsed = 0f + var finished = false + + fun tick(tickDelta: Float): T { + elapsed += tickDelta + val elapsedT = Mth.clamp(elapsed / durationTicks, 0f, 1f) + val eased = easing?.transform(elapsedT) ?: elapsedT + + finished = elapsed >= durationTicks + return interpolator.lerp(eased, from, to) + } + + companion object { + fun int(from: Int, to: Int, duration: Duration, easing: Easing? = null) = Tween(from, to, duration, TweenInterpolator.IntInterpolator, easing) + fun float(from: Float, to: Float, duration: Duration, easing: Easing? = null) = Tween(from, to, duration, TweenInterpolator.FloatInterpolator, easing) + fun double(from: Double, to: Double, duration: Duration, easing: Easing? = null) = Tween(from, to, duration, TweenInterpolator.DoubleInterpolator, easing) + fun vec3(from: Vec3, to: Vec3, duration: Duration, easing: Easing? = null) = Tween(from, to, duration, TweenInterpolator.Vec3Interpolator, easing) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/TweenInterpolator.kt b/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/TweenInterpolator.kt new file mode 100644 index 00000000..e10e4064 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/api/ui/tween/TweenInterpolator.kt @@ -0,0 +1,24 @@ +package dev.asodesu.islandutils.api.ui.tween + +import net.minecraft.util.Mth +import net.minecraft.world.phys.Vec3 + +interface TweenInterpolator { + fun lerp(t: Float, a: T, b: T): T + + object IntInterpolator : TweenInterpolator { + override fun lerp(t: Float, a: Int, b: Int) = Mth.lerp(t, a.toFloat(), b.toFloat()).toInt() + } + + object FloatInterpolator : TweenInterpolator { + override fun lerp(t: Float, a: Float, b: Float) = Mth.lerp(t, a, b) + } + + object DoubleInterpolator : TweenInterpolator { + override fun lerp(t: Float, a: Double, b: Double) = Mth.lerp(t.toDouble(), a, b) + } + + object Vec3Interpolator : TweenInterpolator { + override fun lerp(t: Float, a: Vec3, b: Vec3): Vec3 = Mth.lerp(t.toDouble(), a, b) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/CosmeticUI.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/CosmeticUI.kt new file mode 100644 index 00000000..dbeac174 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/CosmeticUI.kt @@ -0,0 +1,137 @@ +package dev.asodesu.islandutils.cosmetics + +import com.mojang.blaze3d.platform.InputConstants +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyser +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyserFactory +import dev.asodesu.islandutils.api.chest.analysis.ContainerScreenHelper +import dev.asodesu.islandutils.api.chest.anyLineContains +import dev.asodesu.islandutils.api.chest.customItemId +import dev.asodesu.islandutils.api.chest.lore +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.game.inGame +import dev.asodesu.islandutils.cosmetics.item.CosmeticItem +import dev.asodesu.islandutils.cosmetics.types.CosmeticType +import dev.asodesu.islandutils.options.CosmeticOptions +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.resources.Identifier +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.ItemStack +import kotlin.math.ceil + +class CosmeticUI(private val wardrobe: Wardrobe) : ChestAnalyser { + private val PREVIEW_SPRITE = Resources.islandUtils("preview") + private val SELECTED_COSMETIC_LINE = "Click to Unequip" + private val UI_DOLL_BOTTOM_PADDING = 8 + private val UI_DISPLAY_GAP = 4 + + private var isCosmeticMenu = false + private var lastHoveredSlot = -1 + private var lastHoveredItemType = Identifier.withDefaultNamespace("default") + private var previewKeyMapping: Int = InputConstants.MOUSE_BUTTON_MIDDLE + + override fun analyse(item: ItemStack, slot: Int) { + val type = wardrobe.getType(item) ?: return + + // check for new base items + if (item.lore.anyLineContains(SELECTED_COSMETIC_LINE)) { + type.baseItem = item + } + + isCosmeticMenu = true + } + + override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, helper: ContainerScreenHelper) { + if (shouldRender()) return + + // update hovered item + if (CosmeticOptions.showOnHover.get()) { + val hoveredSlot = helper.getHoveredSlot() + val item = hoveredSlot?.item ?: ItemStack.EMPTY + // only trigger updates when we change what slot we're hovering + val hoveredSlotIndex = hoveredSlot?.index ?: -1 + val hoveredItemType = item.customItemId ?: Identifier.withDefaultNamespace("default") + if (lastHoveredSlot != hoveredSlotIndex || lastHoveredItemType != hoveredItemType) { + wardrobe.slots.forEach { it.checkAndUpdateHover(item) } + lastHoveredSlot = hoveredSlotIndex + lastHoveredItemType = hoveredItemType + } + } + + val screen = helper.getScreen() + + val size = ceil(helper.imageHeight / 2.5).toInt() + val bounds = helper.imageHeight + var x = (screen.width - helper.imageWidth) / 3 + var y = (screen.height / 2) + + wardrobe.doll.render( + guiGraphics, + x - bounds, + y - bounds, + x + bounds, + y + bounds, + size + ) + + x -= (size / 2) // offset by size/2 to be placed in the middle of the doll + y += size // offset by size to be placed below the doll + y += UI_DOLL_BOTTOM_PADDING + wardrobe.slots.forEach { + if (!it.renderUI(guiGraphics, x, y, size)) return@forEach + y += CosmeticType.UI_DISPLAY_HEIGHT + UI_DISPLAY_GAP + } + } + + override fun mouseDragged(helper: ContainerScreenHelper, mouseX: Double, mouseY: Double, deltaX: Double, deltaY: Double) { + if (shouldRender()) return + + wardrobe.doll.xRot -= deltaX.toFloat() + wardrobe.doll.yRot -= deltaY.toFloat() + } + + override fun renderSlotFront(guiGraphics: GuiGraphics, helper: ContainerScreenHelper, slot: Slot) { + if (shouldRender()) return + val item = slot.item + val customItemId = item.customItemId ?: return + val type = wardrobe.getType(customItemId) ?: return + + // check if this item is this types preview item + if (type.preview.customItemId != customItemId) return + + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, PREVIEW_SPRITE, slot.x - 3, slot.y - 4, 22, 24) + } + + fun shouldRender(): Boolean { + if (!CosmeticOptions.showInGames.get() && inGame) return false + return !CosmeticOptions.showInAllMenus.get() && !isCosmeticMenu + } + + override fun keyPressed(helper: ContainerScreenHelper, keyCode: Int, scanCode: Int, modifiers: Int) = testPreviewClicked(helper, keyCode) + override fun mouseReleased(helper: ContainerScreenHelper, mouseX: Double, mouseY: Double, keyCode: Int) = testPreviewClicked(helper, keyCode) + private fun testPreviewClicked(helper: ContainerScreenHelper, keyCode: Int) { + if (!isCosmeticMenu) return + if (keyCode != previewKeyMapping) return + + val hoveredSlot = helper.getHoveredSlot() ?: return + val item = hoveredSlot.item ?: return + val cosmeticType = wardrobe.getType(item) ?: return + + // if we are clicking on the same item again, clear the preview for this type + if (cosmeticType.preview.customItemId == item.customItemId) { + cosmeticType.preview = CosmeticItem.empty() + cosmeticType.hover = CosmeticItem.empty() + } else { + cosmeticType.previewItem = item + } + } + + override fun close(helper: ContainerScreenHelper) { + Wardrobe.dispose() + } + + object Factory : ChestAnalyserFactory { + override fun create(menuComponents: Collection) = CosmeticUI(Wardrobe.get()) + override fun shouldApply(menuComponents: Collection) = CosmeticOptions.enabled.get() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/Wardrobe.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/Wardrobe.kt new file mode 100644 index 00000000..d170cbb7 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/Wardrobe.kt @@ -0,0 +1,50 @@ +package dev.asodesu.islandutils.cosmetics + +import dev.asodesu.islandutils.api.chest.customItemId +import dev.asodesu.islandutils.cosmetics.types.AccessoryCosmetic +import dev.asodesu.islandutils.cosmetics.types.CloakCosmetic +import dev.asodesu.islandutils.cosmetics.types.CosmeticType +import dev.asodesu.islandutils.cosmetics.types.HatCosmetic +import dev.asodesu.islandutils.cosmetics.types.SkinCosmetic +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.resources.Identifier +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemStack + +class Wardrobe { + val doll = WardrobeDoll(this) + val slots: List = listOf( + HatCosmetic(), + AccessoryCosmetic(), + SkinCosmetic(), + CloakCosmetic() + ) + + fun apply(guiGraphics: GuiGraphics, livingEntity: LivingEntity) { + slots.forEach { + val item = it.get() + it.render(guiGraphics, livingEntity, item) + } + } + + fun isCosmeticItem(item: ItemStack) = slots.any { it.check(item) } + fun getType(item: ItemStack): CosmeticType? { + if (item.isEmpty) return null + return item.customItemId?.let { getType(it) } + } + fun getType(itemId: Identifier) = slots.firstOrNull { it.check(itemId) } + + companion object { + private var instance: Wardrobe? = null + fun get(): Wardrobe { + if (instance != null) return instance!! + return Wardrobe().also { + instance = it + } + } + + fun dispose() { + instance = null + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/WardrobeDoll.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/WardrobeDoll.kt new file mode 100644 index 00000000..31e17e6f --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/WardrobeDoll.kt @@ -0,0 +1,60 @@ +package dev.asodesu.islandutils.cosmetics + +import dev.asodesu.islandutils.api.extentions.minecraft +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.screens.inventory.InventoryScreen +import net.minecraft.client.player.LocalPlayer +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState +import org.joml.Quaternionf +import org.joml.Vector3f +import kotlin.math.atan + +class WardrobeDoll(private val wardrobe: Wardrobe) { + var xRot = -25f + var yRot = -5f + + fun render(guiGraphics: GuiGraphics, x0: Int, y0: Int, x1: Int, y1: Int, size: Int) { + val player = minecraft.player!! + renderDoll(guiGraphics, x0, y0, x1, y1, size, player) + } + + private fun renderDoll(guiGraphics: GuiGraphics, x0: Int, y0: Int, x1: Int, y1: Int, size: Int, livingEntity: LocalPlayer) { + // store original items + val originalItems = wardrobe.slots.associateWith { it.getFromEntity(livingEntity) } + wardrobe.apply(guiGraphics, livingEntity) + + // appropriated from InventoryScreen#renderEntityInInventoryFollowsMouse + // we have to copy and use our own due to the way our rotations work :3 + val yAngle = atan((yRot / 40.0f).toDouble()).toFloat() + val xAngle = xRot + val rotation = Quaternionf().rotateZ(Math.PI.toFloat()) + val xRotation = Quaternionf().rotateX(yAngle * 20.0f * (Math.PI / 180.0).toFloat()) + rotation.mul(xRotation) + val entityRenderState = InventoryScreen.extractRenderState(livingEntity) + if (entityRenderState is LivingEntityRenderState) { + entityRenderState.bodyRot = 180f + xAngle + entityRenderState.yRot = 0f + entityRenderState.xRot = -yAngle * 20.0f + entityRenderState.boundingBoxWidth /= entityRenderState.scale + entityRenderState.boundingBoxHeight /= entityRenderState.scale + entityRenderState.scale = 1.0f + } + + val translation = Vector3f(0.0f, entityRenderState.boundingBoxHeight / 2.0f + 0.0625f, 0.0f) + guiGraphics.submitEntityRenderState( + entityRenderState, + size.toFloat(), + translation, + rotation, + xRotation, + x0, + y0, + x1, + y1 + ) + + // restore original items + originalItems.forEach { (type, item) -> type.setToEntity(livingEntity, item) } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/cloak/CloakRenderLayer.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/cloak/CloakRenderLayer.kt new file mode 100644 index 00000000..8cbcdaba --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/cloak/CloakRenderLayer.kt @@ -0,0 +1,45 @@ +package dev.asodesu.islandutils.cosmetics.cloak + +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.math.Axis +import net.minecraft.client.model.player.PlayerModel +import net.minecraft.client.renderer.SubmitNodeCollector +import net.minecraft.client.renderer.entity.EntityRendererProvider +import net.minecraft.client.renderer.entity.RenderLayerParent +import net.minecraft.client.renderer.entity.layers.RenderLayer +import net.minecraft.client.renderer.entity.state.AvatarRenderState +import net.minecraft.client.renderer.item.ItemStackRenderState +import net.minecraft.client.renderer.texture.OverlayTexture +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemDisplayContext +import net.minecraft.world.item.ItemStack + +class CloakRenderLayer( + parent: RenderLayerParent, + context: EntityRendererProvider.Context +) : RenderLayer(parent) { + companion object { + var cloakForNextRender: ItemStack? = null + var playerEntityForNextRender: LivingEntity? = null + } + private val itemModelResolver = context.itemModelResolver + private val cloakItem = ItemStackRenderState() + + override fun submit(poseStack: PoseStack, submitNodeCollector: SubmitNodeCollector, i: Int, entityRenderState: AvatarRenderState, f: Float, g: Float) { + val cloak = cloakForNextRender ?: return + cloakForNextRender = null + + val player = playerEntityForNextRender ?: return + playerEntityForNextRender = null + + val entityModel = this.parentModel + entityModel.root().translateAndRotate(poseStack) + entityModel.body.translateAndRotate(poseStack) + poseStack.translate(0f, -2.1f, 0f) + poseStack.mulPose(Axis.YP.rotationDegrees(180.0f)) + poseStack.scale(0.625f, -0.625f, -0.625f) + itemModelResolver.updateForLiving(cloakItem, cloak, ItemDisplayContext.HEAD, player) + cloakItem.submit(poseStack, submitNodeCollector, i, OverlayTexture.NO_OVERLAY, entityRenderState.outlineColor) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/item/CosmeticItem.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/item/CosmeticItem.kt new file mode 100644 index 00000000..f5720dfd --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/item/CosmeticItem.kt @@ -0,0 +1,43 @@ +package dev.asodesu.islandutils.cosmetics.item + +import dev.asodesu.islandutils.api.chest.customItemId +import dev.asodesu.islandutils.api.chest.lore +import dev.asodesu.islandutils.api.extentions.Resources +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TextColor +import net.minecraft.world.item.ItemStack + +class CosmeticItem(val item: ItemStack) { + companion object { + private val COSMETIC_DESCRIPTION_COLOR = TextColor.parseColor("#768888").orThrow + fun empty() = CosmeticItem(ItemStack.EMPTY) + } + + val isEmpty = item.isEmpty + val customItemId = item.customItemId ?: Resources.islandUtils("empty") + val tooltip: List + val badge: Component? + + init { + if (isEmpty) { + tooltip = emptyList() + badge = null + } else { + val lore = item.lore + val itemName = item.hoverName + val rarityAndType = lore.firstOrNull() ?: Component.empty() + + val descriptionLines = lore.filter { loreLine -> + loreLine.siblings.isNotEmpty() && loreLine.siblings.all { it.string.isBlank() || it.style.isItalic && it.style.color == COSMETIC_DESCRIPTION_COLOR } + } + + tooltip = buildList { + add(itemName) + add(rarityAndType) + add(Component.empty()) + addAll(descriptionLines) + } + badge = rarityAndType.toFlatList().getOrNull(1) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/AccessoryCosmetic.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/AccessoryCosmetic.kt new file mode 100644 index 00000000..d955cd61 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/AccessoryCosmetic.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.cosmetics.types + +import dev.asodesu.islandutils.Font +import dev.asodesu.islandutils.api.extentions.minecraft +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemStack + +class AccessoryCosmetic : CosmeticType() { + override val names = listOf("accessory") + override val badge: Component = Font.ACCESSORY_BADGE + override var base = getFromEntity(minecraft.player!!).toCosmetic() + + override fun getFromEntity(entity: LivingEntity): ItemStack = entity.getItemBySlot(EquipmentSlot.OFFHAND) + override fun setToEntity(entity: LivingEntity, item: ItemStack) = entity.setItemSlot(EquipmentSlot.OFFHAND, item) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/CloakCosmetic.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/CloakCosmetic.kt new file mode 100644 index 00000000..ca9c251f --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/CloakCosmetic.kt @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.cosmetics.types + +import dev.asodesu.islandutils.Font +import dev.asodesu.islandutils.cosmetics.cloak.CloakRenderLayer +import dev.asodesu.islandutils.cosmetics.item.CosmeticItem +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemStack + +class CloakCosmetic : CosmeticType() { + override val names = listOf("back") + override val badge: Component = Font.CLOAK_BADGE + override var base = CosmeticItem.empty() + + override fun render(guiGraphics: GuiGraphics, entity: LivingEntity, cosmeticItem: CosmeticItem) { + CloakRenderLayer.cloakForNextRender = if (cosmeticItem.isEmpty) null else cosmeticItem.item + CloakRenderLayer.playerEntityForNextRender = entity + } + + override fun getFromEntity(entity: LivingEntity): ItemStack = ItemStack.EMPTY + override fun setToEntity(entity: LivingEntity, item: ItemStack) {} + override fun renderUI(guiGraphics: GuiGraphics, x: Int, y: Int, width: Int, height: Int, cosmeticItem: CosmeticItem): Boolean { + if (cosmeticItem.isEmpty) return false + return super.renderUI(guiGraphics, x, y, width, height, cosmeticItem) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/CosmeticType.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/CosmeticType.kt new file mode 100644 index 00000000..3a568314 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/CosmeticType.kt @@ -0,0 +1,92 @@ +package dev.asodesu.islandutils.cosmetics.types + +import dev.asodesu.islandutils.api.chest.customItemId +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.isInsideBox +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.extentions.pose +import dev.asodesu.islandutils.cosmetics.item.CosmeticItem +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.network.chat.Component +import net.minecraft.resources.Identifier +import net.minecraft.util.ARGB +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemStack + +abstract class CosmeticType { + companion object { + val BACKGROUND_SPRITE = Resources.islandUtils("widget/rounded_1") + val BACKGROUND_COLOR = ARGB.color(76, 0, 0, 0) + const val UI_BADGE_PADDING_X = 4 + const val UI_ITEM_PADDING = 2 + const val UI_DISPLAY_HEIGHT = 20 + const val ITEM_SIZE = 16 + } + + abstract val names: List + abstract val badge: Component? + + open var base: CosmeticItem = CosmeticItem.empty() + var baseItem: ItemStack + get() = base.item + set(value) { base = value.toCosmetic() } + + open var hover: CosmeticItem = CosmeticItem.empty() + var hoverItem: ItemStack + get() = hover.item + set(value) { hover = value.toCosmetic() } + + open var preview: CosmeticItem = CosmeticItem.empty() + var previewItem: ItemStack + get() = preview.item + set(value) { preview = value.toCosmetic() } + + abstract fun getFromEntity(entity: LivingEntity): ItemStack + abstract fun setToEntity(entity: LivingEntity, item: ItemStack) + open fun render(guiGraphics: GuiGraphics, entity: LivingEntity, cosmeticItem: CosmeticItem) = setToEntity(entity, cosmeticItem.item) + + open fun renderUI(guiGraphics: GuiGraphics, x: Int, y: Int, width: Int, height: Int = UI_DISPLAY_HEIGHT, cosmeticItem: CosmeticItem = get()): Boolean { + val badge = badge ?: return false + val font = minecraft.font + + val itemX = width - ITEM_SIZE - UI_ITEM_PADDING + val itemY = UI_ITEM_PADDING + + guiGraphics.pose { + translate(x.toFloat(), y.toFloat()) + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, BACKGROUND_SPRITE, 0, 0, width, height, BACKGROUND_COLOR) + guiGraphics.drawString(font, badge, UI_BADGE_PADDING_X, ((height - font.lineHeight) / 2), ARGB.white(1f)) + guiGraphics.renderItem(cosmeticItem.item, itemX, itemY) + } + + val mouseHandler = minecraft.mouseHandler + val window = minecraft.window + val xPos = mouseHandler.getScaledXPos(window).toInt() + val yPos = mouseHandler.getScaledYPos(window).toInt() + if (isInsideBox(xPos, yPos, x + itemX, y + itemY, ITEM_SIZE, ITEM_SIZE)) { + guiGraphics.setComponentTooltipForNextFrame(minecraft.font, cosmeticItem.tooltip, xPos, yPos) + } + return true + } + + fun checkAndUpdateHover(item: ItemStack) { + hover = if (check(item)) item.toCosmetic() + else CosmeticItem.empty() + } + + fun get(): CosmeticItem { + if (!hover.isEmpty) return hover + if (!preview.isEmpty) return preview + if (!base.isEmpty) return base + return CosmeticItem.empty() + } + + fun check(item: ItemStack): Boolean { + val itemId = item.customItemId ?: return false + return check(itemId) + } + fun check(itemId: Identifier) = names.any { !itemId.path.endsWith("icon_empty") && itemId.path.contains("/$it/") } + + fun ItemStack.toCosmetic() = CosmeticItem(this) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/HatCosmetic.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/HatCosmetic.kt new file mode 100644 index 00000000..4eb8174d --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/HatCosmetic.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.cosmetics.types + +import dev.asodesu.islandutils.Font +import dev.asodesu.islandutils.api.extentions.minecraft +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemStack + +class HatCosmetic : CosmeticType() { + override val names = listOf("hat", "hair") + override val badge: Component = Font.HAT_BADGE + override var base = getFromEntity(minecraft.player!!).toCosmetic() + + override fun getFromEntity(entity: LivingEntity): ItemStack = entity.getItemBySlot(EquipmentSlot.HEAD) + override fun setToEntity(entity: LivingEntity, item: ItemStack) = entity.setItemSlot(EquipmentSlot.HEAD, item) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/SkinCosmetic.kt b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/SkinCosmetic.kt new file mode 100644 index 00000000..25e808a0 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/cosmetics/types/SkinCosmetic.kt @@ -0,0 +1,25 @@ +package dev.asodesu.islandutils.cosmetics.types + +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.game.activeGame +import dev.asodesu.islandutils.cosmetics.item.CosmeticItem +import dev.asodesu.islandutils.games.Fishing +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemStack + +class SkinCosmetic : CosmeticType() { + override val names = listOf("rods", "weapon_skins") + override val badge: Component get() = get().badge ?: Component.empty() + override var base = if (activeGame is Fishing) getFromEntity(minecraft.player!!).toCosmetic() else CosmeticItem.empty() + + override fun getFromEntity(entity: LivingEntity): ItemStack = entity.getItemBySlot(EquipmentSlot.MAINHAND) + override fun setToEntity(entity: LivingEntity, item: ItemStack) = entity.setItemSlot(EquipmentSlot.MAINHAND, item) + + override fun renderUI(guiGraphics: GuiGraphics, x: Int, y: Int, width: Int, height: Int, cosmeticItem: CosmeticItem): Boolean { + if (cosmeticItem.isEmpty) return false + return super.renderUI(guiGraphics, x, y, width, height, cosmeticItem) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/ClassicHitw.kt b/src/main/kotlin/dev/asodesu/islandutils/features/ClassicHitw.kt new file mode 100644 index 00000000..9ff4c234 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/ClassicHitw.kt @@ -0,0 +1,124 @@ +package dev.asodesu.islandutils.features + +import dev.asodesu.islandutils.api.Debounce +import dev.asodesu.islandutils.api.events.sound.SoundEvents +import dev.asodesu.islandutils.api.events.sound.SoundPlayCallback +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.extentions.play +import dev.asodesu.islandutils.api.extentions.style +import dev.asodesu.islandutils.api.extentions.toSoundEvent +import dev.asodesu.islandutils.api.game.activeGame +import dev.asodesu.islandutils.api.modules.Module +import dev.asodesu.islandutils.api.music.resources.handler.DownloadJob +import dev.asodesu.islandutils.games.HoleInTheWall +import dev.asodesu.islandutils.options.ClassicHitwOptions +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TextColor +import kotlin.time.Duration.Companion.seconds + +object ClassicHitw : Module("ClassicHitw") { + const val MUSIC_ASSET = "classic_hitw/music" + + val MUSIC_DOWNLOAD = DownloadJob.single(MUSIC_ASSET) + val ANNOUNCER_DOWNLOAD = DownloadJob.multi( + "classic_hitw/announcer/archery", + "classic_hitw/announcer/arrowstorm", + "classic_hitw/announcer/bombsquad", + "classic_hitw/announcer/bonuspoints", + "classic_hitw/announcer/booby", + "classic_hitw/announcer/bumperbars", + "classic_hitw/announcer/chickentornado", + "classic_hitw/announcer/erosion", + "classic_hitw/announcer/flowerhead", + "classic_hitw/announcer/gameover", + "classic_hitw/announcer/gettingdizzy", + "classic_hitw/announcer/hightide", + "classic_hitw/announcer/hmm", + "classic_hitw/announcer/hotcoals", + "classic_hitw/announcer/jackfrost", + "classic_hitw/announcer/jungle", + "classic_hitw/announcer/kaboom", + "classic_hitw/announcer/letters", + "classic_hitw/announcer/loser", + "classic_hitw/announcer/marathon", + "classic_hitw/announcer/matrix", + "classic_hitw/announcer/molasses", + "classic_hitw/announcer/myeyes", + "classic_hitw/announcer/one", + "classic_hitw/announcer/plugyourears", + "classic_hitw/announcer/reproduction", + "classic_hitw/announcer/revenge", + "classic_hitw/announcer/solonely", + "classic_hitw/announcer/stickyshoes", + "classic_hitw/announcer/superspeed", + "classic_hitw/announcer/swimmyfish", + "classic_hitw/announcer/title", + "classic_hitw/announcer/whatintheblazes", + "classic_hitw/announcer/whereami", + ) + + private val TRAP_COLOR: TextColor = TextColor.parseColor("#FFA800").orThrow + private val TRAP_REPLACEMENTS = mutableMapOf( + "Feeling Hot" to "What in the Blazes", + "Hot Coals" to "Feeling Hot", + "Blast-Off" to "Kaboom", + "Pillagers" to "So Lonely", + "Leg Day" to "Molasses", + "Snowball Fight" to "Jack Frost", + ) + private val GAME_OVER_SOUNDS = listOf( + "games.global.objective.eliminated", + "games.global.timer.round_end" + ) + private val TRAP_SOUND_REGEX = "([ \\-!])".toRegex() + + override fun init() { + SoundEvents.SOUND_PLAY.register(::handleSound) + + if (ClassicHitwOptions.annoucer.get()) ANNOUNCER_DOWNLOAD.start() + } + + // called from mixin + fun handleSubtitle(component: Component): Boolean { + if (!ClassicHitwOptions.annoucer.get()) return false + val isTrap = component.toFlatList().any { + val style = it.style + !style.isObfuscated && style.color == TRAP_COLOR + } + if (!isTrap) return false + + var trap = component.string + val replacementTrap = TRAP_REPLACEMENTS[trap] + if (replacementTrap != null) { + trap = replacementTrap + minecraft.gui.setSubtitle(Component.literal(replacementTrap).style { withColor(TRAP_COLOR) }) + } + + try { + playTrapSound(trap.replace(TRAP_SOUND_REGEX,"").lowercase()) + } catch (e: Exception) { + } + + return replacementTrap != null + } + + private val gameOverDebounce = Debounce(1.seconds) + fun handleSound(info: SoundInfo, ci: SoundPlayCallback.Info) { + if (activeGame !is HoleInTheWall) return + + // check for round end sound + val name = info.sound + if (name.namespace != "mcc") return + if (!GAME_OVER_SOUNDS.contains(name.path)) return + if (!gameOverDebounce.consume()) return + + playTrapSound("gameover") + } + + private fun playTrapSound(trapSoundKey: String) { + val soundEvent = Resources.islandUtils("classic_hitw.announcer.$trapSoundKey").toSoundEvent() + minecraft.soundManager.play(soundEvent) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/CommandKeybind.kt b/src/main/kotlin/dev/asodesu/islandutils/features/CommandKeybind.kt new file mode 100644 index 00000000..d4eed154 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/CommandKeybind.kt @@ -0,0 +1,43 @@ +package dev.asodesu.islandutils.features + +import com.mojang.blaze3d.platform.InputConstants +import dev.asodesu.islandutils.IslandUtils +import dev.asodesu.islandutils.api.Debounce +import dev.asodesu.islandutils.api.extentions.isOnline +import dev.asodesu.islandutils.api.modules.Module +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper +import net.minecraft.client.KeyMapping +import net.minecraft.client.Minecraft +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +class CommandKeybind( + keybindName: String, + key: Int, + private val command: String, + type: InputConstants.Type = InputConstants.Type.KEYSYM, + debounceTime: Duration = 1.seconds +) : Module("CmdKeybind-$keybindName") { + private val keyMapping = KeyMapping( + "key.islandutils.$keybindName", + type, + key, + IslandUtils.keyMappingCategory + ) + private val debounce = Debounce(debounceTime) + + override fun init() { + KeyBindingHelper.registerKeyBinding(keyMapping) + ClientTickEvents.END_CLIENT_TICK.register(::tick) + } + + private fun tick(client: Minecraft) { + if (!keyMapping.consumeClick()) return + + val player = client.player?.connection ?: return + if (!isOnline || !debounce.consume()) return + + player.sendCommand(command) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/ConfirmDisconnectScreen.kt b/src/main/kotlin/dev/asodesu/islandutils/features/ConfirmDisconnectScreen.kt new file mode 100644 index 00000000..2a0ce496 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/ConfirmDisconnectScreen.kt @@ -0,0 +1,37 @@ +package dev.asodesu.islandutils.features + +import dev.asodesu.islandutils.api.ui.background +import net.minecraft.client.gui.components.Button +import net.minecraft.client.gui.components.StringWidget +import net.minecraft.client.gui.layouts.FrameLayout +import net.minecraft.client.gui.layouts.LinearLayout +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component.translatable + +class ConfirmDisconnectScreen( + private val parent: Screen, + private val disconnect: Button.OnPress +) : Screen(translatable("islandutils.confirm_disconnect.title")) { + + override fun init() { + val frame = FrameLayout().apply { + addRenderableOnly(background(spacing = 12, opacity = .5f)) + addChild(LinearLayout.vertical().spacing(10).apply { + addChild(StringWidget(title, font)) { it.alignHorizontallyCenter() } + addChild(LinearLayout.horizontal().spacing(10).apply { + addChild(Button.builder(translatable("gui.cancel")) { onClose() }.width(120).build()) + addChild(Button.builder(translatable("islandutils.confirm_disconnect.disconnect"), disconnect).width(120).build()) + }) + }) + } + + frame.arrangeElements() + FrameLayout.centerInRectangle(frame, this.rectangle) + frame.visitWidgets { this.addRenderableWidget(it) } + } + + override fun onClose() { + minecraft?.setScreen(parent) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/FishingUpgradeHighlight.kt b/src/main/kotlin/dev/asodesu/islandutils/features/FishingUpgradeHighlight.kt new file mode 100644 index 00000000..0cf24982 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/FishingUpgradeHighlight.kt @@ -0,0 +1,31 @@ +package dev.asodesu.islandutils.features + +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyser +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyserFactory +import dev.asodesu.islandutils.api.chest.analysis.ContainerScreenHelper +import dev.asodesu.islandutils.api.chest.anyLineContains +import dev.asodesu.islandutils.api.chest.loreOrNull +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.options.MiscOptions +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.resources.Identifier +import net.minecraft.world.inventory.Slot + +class FishingUpgradeHighlight : ChestAnalyser { + private val UPGRADE_SPRITE = Resources.islandUtils("upgrade") + private val UPGRADE_LORE_LINE = "Left-Click to Upgrade" + + override fun renderSlotFront(guiGraphics: GuiGraphics, helper: ContainerScreenHelper, slot: Slot) { + if (!slot.hasItem()) return + val lores = slot.item.loreOrNull ?: return + if (!lores.anyLineContains(UPGRADE_LORE_LINE)) return + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, UPGRADE_SPRITE, slot.x + 1, slot.y + 1, 16, 16) + } + + companion object : ChestAnalyserFactory { + private val enabled by MiscOptions.fishingUpgrades + override fun shouldApply(menuComponents: Collection) = enabled + override fun create(menuComponents: Collection) = FishingUpgradeHighlight() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/FriendsInGame.kt b/src/main/kotlin/dev/asodesu/islandutils/features/FriendsInGame.kt new file mode 100644 index 00000000..b3d9d307 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/FriendsInGame.kt @@ -0,0 +1,75 @@ +package dev.asodesu.islandutils.features + +import dev.asodesu.islandutils.Font +import dev.asodesu.islandutils.api.Scheduler.runAfter +import dev.asodesu.islandutils.api.extentions.buildComponent +import dev.asodesu.islandutils.api.extentions.connection +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.extentions.send +import dev.asodesu.islandutils.api.game.GameEvents +import dev.asodesu.islandutils.api.game.inLobby +import dev.asodesu.islandutils.api.modules.Module +import dev.asodesu.islandutils.api.server.ServerEvents +import dev.asodesu.islandutils.options.NotificationOptions +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket.Entry +import net.minecraft.network.protocol.game.ServerboundCommandSuggestionPacket +import kotlin.time.Duration.Companion.seconds + +object FriendsInGame : Module("FriendsInGame") { + const val TRANSACTION_ID = 6775161 + + private val enabledInGame by NotificationOptions.FriendsInGame.inGame + private val enabledInLobby by NotificationOptions.FriendsInGame.inLobby + private var sentInInstance = false + + var friends = listOf() + + override fun init() { + GameEvents.SERVER_UPDATE.register { + // don't bother if we have it disabled, or we've already sent for this instance + if (!enabledInLobby && !enabledInGame && !sentInInstance) return@register + val commandSuggestion = ServerboundCommandSuggestionPacket(TRANSACTION_ID, "/friend remove ") + minecraft.connection?.send(commandSuggestion) + } + + // reset sendInInstance to false when we switch instances to avoid duplication + ServerEvents.INSTANCE_JOIN.register { + sentInInstance = false + } + } + + // called from mixin + fun receiveSuggestionCallback(entries: List) { + friends = entries.map { it.text } + if (inLobby && !enabledInLobby) return // return if we're in lobby and lobby is disabled + if (!enabledInGame) return // we're in game, if it's disabled in game, return + + runAfter(1.75.seconds) { sendFriends() } + } + + private fun sendFriends() { + val connection = connection ?: return + // create a list of the usernames of everyone on this server who is friends + val onlineFriendNames = connection.onlinePlayers.mapNotNull map@ { + val name = it.profile.name + if (!friends.contains(name)) null + else name + } + if (onlineFriendNames.isEmpty()) return + + val friendString = onlineFriendNames.joinToString(", ") + val lang = if (inLobby) "lobby" else "game" + + val component = buildComponent { + withStyle { it.withColor(ChatFormatting.GREEN) } + + append(Component.literal("[").append(Font.SOCIAL_ICON).append("] ")) + append(Component.translatable("islandutils.feature.friends.$lang").append(": ")) + append(Component.literal(friendString).withStyle(ChatFormatting.YELLOW)) + } + + send(component) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/RemnantHighlight.kt b/src/main/kotlin/dev/asodesu/islandutils/features/RemnantHighlight.kt new file mode 100644 index 00000000..001d7a5b --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/RemnantHighlight.kt @@ -0,0 +1,24 @@ +package dev.asodesu.islandutils.features + +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyser +import dev.asodesu.islandutils.api.chest.analysis.ContainerScreenHelper +import dev.asodesu.islandutils.api.chest.anyLineContains +import dev.asodesu.islandutils.api.chest.loreOrNull +import dev.asodesu.islandutils.api.extentions.rect +import dev.asodesu.islandutils.options.MiscOptions +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.util.ARGB +import net.minecraft.world.inventory.Slot + +object RemnantHighlight : ChestAnalyser { + private const val REMNANT_STRING = "This item is the remnant of an item" + private val REMNANT_HIGHLIGHT_COLOR = ARGB.color(192, 141, 65, 100) + private val enabled by MiscOptions.remnantHighlight + + override fun renderSlotBack(guiGraphics: GuiGraphics, helper: ContainerScreenHelper, slot: Slot) { + if (!enabled || !slot.hasItem()) return + val lores = slot.item.loreOrNull ?: return + if (!lores.anyLineContains(REMNANT_STRING)) return + guiGraphics.rect(slot.x, slot.y, 16, 16, REMNANT_HIGHLIGHT_COLOR) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/ChatButtons.kt b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/ChatButtons.kt new file mode 100644 index 00000000..a2414d55 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/ChatButtons.kt @@ -0,0 +1,39 @@ +package dev.asodesu.islandutils.features.chatbuttons + +import dev.asodesu.islandutils.api.Debounce +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.game.activeGame +import dev.asodesu.islandutils.features.chatbuttons.button.ChannelButton +import dev.asodesu.islandutils.features.chatbuttons.button.SpriteChannelButton +import net.minecraft.util.ARGB +import kotlin.time.Duration.Companion.seconds + +object ChatButtons { + val UNDERLINE_COLOR = ARGB.white(1f) + val BUTTON_WIDTH = 43 + val BUTTON_HEIGHT = 9 + val INPUT_GAP = 2 + val BUTTON_GAP = 3 + + val LOCAL = SpriteChannelButton("local", Resources.islandUtils("chat_button/local")) + val PARTY = SpriteChannelButton("party", Resources.islandUtils("chat_button/party")) + val TEAM = SpriteChannelButton("team", Resources.islandUtils("chat_button/team")) + val PLOBBY = SpriteChannelButton("plobby", Resources.islandUtils("chat_button/plobby")) + + private val channelCommandDebouce = Debounce(1.seconds) + + fun currentButtons(): List { + val buttons = mutableListOf(LOCAL, PARTY) + if (activeGame.hasTeamChat) buttons.add(TEAM) + // TODO: Plobby state detection + + return buttons + } + + fun switchChannel(channel: String) { + if (!channelCommandDebouce.consume()) return + minecraft.connection?.sendCommand("chat $channel") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ChannelButton.kt b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ChannelButton.kt new file mode 100644 index 00000000..43cc967b --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ChannelButton.kt @@ -0,0 +1,10 @@ +package dev.asodesu.islandutils.features.chatbuttons.button + +import dev.asodesu.islandutils.features.chatbuttons.ChatButtons +import net.minecraft.client.gui.components.AbstractWidget + +interface ChannelButton { + val channel: String + fun widget(): AbstractWidget + fun onPress() = ChatButtons.switchChannel(channel) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ChatButtonWidget.kt b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ChatButtonWidget.kt new file mode 100644 index 00000000..e059e8a5 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ChatButtonWidget.kt @@ -0,0 +1,35 @@ +package dev.asodesu.islandutils.features.chatbuttons.button + +import dev.asodesu.islandutils.api.extentions.rect +import dev.asodesu.islandutils.features.chatbuttons.ChatButtons +import net.minecraft.client.gui.ComponentPath +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.AbstractButton +import net.minecraft.client.gui.narration.NarrationElementOutput +import net.minecraft.client.gui.navigation.FocusNavigationEvent +import net.minecraft.client.input.InputWithModifiers +import net.minecraft.network.chat.Component + +abstract class ChatButtonWidget( + x: Int, + y: Int, + width: Int, + height: Int, + component: Component = Component.empty() +) : AbstractButton(x, y, width, height, component) { + abstract val channelButton: ChannelButton + + protected fun checkAndRenderUnderline(guiGraphics: GuiGraphics) { + if (isHoveredOrFocused) guiGraphics.rect(this.x, this.y + this.height - 1, this.width, 1, ChatButtons.UNDERLINE_COLOR) + } + + override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) { + this.defaultButtonNarrationText(narrationElementOutput) + } + + override fun nextFocusPath(focusNavigationEvent: FocusNavigationEvent): ComponentPath? { + return null + } + + override fun onPress(inputWithModifiers: InputWithModifiers) = channelButton.onPress() +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ComponentChannelButton.kt b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ComponentChannelButton.kt new file mode 100644 index 00000000..bebe1a3a --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/ComponentChannelButton.kt @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.features.chatbuttons.button + +import dev.asodesu.islandutils.api.extentions.minecraft +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import net.minecraft.util.ARGB + +class ComponentChannelButton(override val channel: String, private val component: Component) : ChannelButton { + override fun widget() = Widget(this, component) + + class Widget( + override val channelButton: ComponentChannelButton, + private val component: Component, + x: Int = 0, + y: Int = 0, + width: Int = 43, + height: Int = 9 + ) : ChatButtonWidget(x, y, width, height) { + private val font = minecraft.font + private val tintColour = ARGB.white(1f) + + override fun renderContents(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + guiGraphics.drawString(font, component, this.x, this.y, tintColour, false) + this.checkAndRenderUnderline(guiGraphics) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/SpriteChannelButton.kt b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/SpriteChannelButton.kt new file mode 100644 index 00000000..36cf70cc --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/chatbuttons/button/SpriteChannelButton.kt @@ -0,0 +1,24 @@ +package dev.asodesu.islandutils.features.chatbuttons.button + +import dev.asodesu.islandutils.features.chatbuttons.ChatButtons +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.resources.Identifier + +class SpriteChannelButton(override val channel: String, private val sprite: Identifier) : ChannelButton { + override fun widget() = Widget(this, sprite) + + class Widget( + override val channelButton: SpriteChannelButton, + private val sprite: Identifier, + x: Int = 0, + y: Int = 0, + width: Int = ChatButtons.BUTTON_WIDTH, + height: Int = ChatButtons.BUTTON_HEIGHT + ) : ChatButtonWidget(x, y, width, height) { + override fun renderContents(guiGraphics: GuiGraphics, i: Int, j: Int, f: Float) { + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, this.x, this.y, this.width, this.height) + this.checkAndRenderUnderline(guiGraphics) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/crafting/Command.kt b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/Command.kt new file mode 100644 index 00000000..0869288f --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/Command.kt @@ -0,0 +1,67 @@ +package dev.asodesu.islandutils.features.crafting + +import com.mojang.brigadier.arguments.IntegerArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import dev.asodesu.islandutils.api.extentions.debugMode +import dev.asodesu.islandutils.api.extentions.style +import dev.asodesu.islandutils.features.crafting.items.CraftingItem +import dev.asodesu.islandutils.features.crafting.items.SavedCraftingItems +import dev.asodesu.islandutils.features.crafting.notif.CraftingNotifier +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal +import net.minecraft.core.component.DataComponents +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TextColor +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.component.CustomModelData + +fun craftingCommand() = literal("crafting") + .executes { ctx -> + ctx.source.sendFeedback(CraftingNotifier.commandResponse()) + 1 + } + +fun craftingDebugCommand() = literal("crafting") + .then(craftingDebugAddCraftCommand()) + +enum class RarityColors(color: String) { + Common("#FFFFFF"), + Uncommon("#1EFF00"), + Rare("#0070DD"), + Epic("#A335EE"), + Legendary("#FF8000"), + Mythic("#F94242"); + + val textColor = TextColor.parseColor(color).orThrow +} +private fun craftingDebugAddCraftCommand() = literal("add_craft") + .then(argument("rarity", IntegerArgumentType.integer(0, 5)) + .then(argument("menu", IntegerArgumentType.integer(0, 1)) + .then(argument("name", StringArgumentType.string()) + .then(argument("delay", IntegerArgumentType.integer()) + .then(argument("slot", IntegerArgumentType.integer()) + .executes { ctx -> + if (!debugMode) return@executes 0 + + val rarity = RarityColors.entries[ctx.getArgument("rarity", Int::class.javaObjectType)] + val menuType = CraftingMenuType.entries[ctx.getArgument("menu", Int::class.javaObjectType)] + val name = ctx.getArgument("name", String::class.java) + val delay = ctx.getArgument("delay", Int::class.javaObjectType) + val slot = ctx.getArgument("slot", Int::class.javaObjectType) + + val item = ItemStack(Items.ECHO_SHARD).apply { + set(DataComponents.CUSTOM_MODEL_DATA, CustomModelData(listOf(9415f), emptyList(), emptyList(), emptyList())) + set(DataComponents.ITEM_NAME, Component.literal(name).style { withColor(rarity.textColor) }) + } + + val craftingItem = CraftingItem( + type = menuType, + finishTimestamp = System.currentTimeMillis() + (delay * 1000L), + slot = slot, + item = item + ) + SavedCraftingItems.add(craftingItem) + + 1 + }))))) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/crafting/CraftingChestAnalyser.kt b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/CraftingChestAnalyser.kt new file mode 100644 index 00000000..74502902 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/CraftingChestAnalyser.kt @@ -0,0 +1,63 @@ +package dev.asodesu.islandutils.features.crafting + +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyser +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyserFactory +import dev.asodesu.islandutils.api.chest.anyLineContains +import dev.asodesu.islandutils.api.chest.customItemId +import dev.asodesu.islandutils.api.chest.lore +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.extentions.getTimeSeconds +import dev.asodesu.islandutils.features.crafting.items.CraftingItem +import dev.asodesu.islandutils.features.crafting.items.SavedCraftingItems +import net.minecraft.network.chat.TextColor +import net.minecraft.resources.Identifier +import net.minecraft.world.item.ItemStack + +class CraftingChestAnalyser(val type: CraftingMenuType) : ChestAnalyser { + companion object { + private val INPUT_SLOT_RANGE = 19..23 + private val TIME_LEFT_COLOR = TextColor.parseColor("#FF5556").orThrow + private const val ACTIVE_CHECK_STRING = "Shift-Click to Cancel" + } + + override fun analyse(item: ItemStack, slot: Int) { + if (slot !in INPUT_SLOT_RANGE) return + + val lores = item.lore + if (lores.isEmpty() || !lores.anyLineContains(ACTIVE_CHECK_STRING)) return removeItem(slot) + val customItemId = item.customItemId ?: return removeItem(slot) + + // find the "Time Left: " lore line based on it's first child's colour + val timeLeftComponent = lores.firstOrNull { it.siblings.firstOrNull()?.style?.color == TIME_LEFT_COLOR } ?: return removeItem(slot) + // add 60 because we don't have the seconds remaining, so we add 60 to assume the worst + val timeLeft = (timeLeftComponent.string.getTimeSeconds() ?: return removeItem(slot)) + 60 + val finishTimestamp = System.currentTimeMillis() + (timeLeft * 1000L) + + val craftingItem = CraftingItem( + type = type, + finishTimestamp = finishTimestamp, + slot = slot, + item = CraftingItem.stripItem(item) + ) + SavedCraftingItems.add(craftingItem) + + debug("[#$slot $type] Found active craft: ${item.displayName.string} + ($timeLeft)") + } + + fun removeItem(slot: Int) { + SavedCraftingItems.remove(type, slot) + debug("$type - Found empty craft slot: $slot!") + } + + object Factory : ChestAnalyserFactory { + override fun shouldApply(menuComponents: Collection): Boolean { + return CraftingMenuType.entries.any { menuComponents.contains(it.menuComponent) } + } + + override fun create(menuComponents: Collection): ChestAnalyser { + val type = CraftingMenuType.entries.first { menuComponents.contains(it.menuComponent) } + return CraftingChestAnalyser(type) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/crafting/CraftingMenuType.kt b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/CraftingMenuType.kt new file mode 100644 index 00000000..d64cb522 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/CraftingMenuType.kt @@ -0,0 +1,13 @@ +package dev.asodesu.islandutils.features.crafting + +import dev.asodesu.islandutils.Font +import dev.asodesu.islandutils.api.extentions.Resources +import net.minecraft.network.chat.Component +import net.minecraft.resources.Identifier + +enum class CraftingMenuType(path: String, val icon: Component) { + ASSEMBLER("_fonts/body/blueprint_assembly.png", Font.ASSEMBLER_ICON), + FORGE("_fonts/body/fusion_forge.png", Font.FUSION_ICON); + + val menuComponent: Identifier = Resources.mcc(path) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/crafting/items/CraftingItem.kt b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/items/CraftingItem.kt new file mode 100644 index 00000000..55d3a28a --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/items/CraftingItem.kt @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.features.crafting.items + +import dev.asodesu.islandutils.features.crafting.CraftingMenuType +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import net.minecraft.core.component.DataComponents +import net.minecraft.world.item.ItemStack + +@Serializable +data class CraftingItem( + val type: CraftingMenuType, + val finishTimestamp: Long, + val slot: Int, + @Contextual val item: ItemStack, + var notified: Boolean = false, +) { + companion object { + private val STRIPPED_COMPONENTS = listOf(DataComponents.LORE, DataComponents.TOOLTIP_DISPLAY) + fun stripItem(item: ItemStack): ItemStack { + return item.copy().also { strippedItem -> + STRIPPED_COMPONENTS.forEach { strippedItem.remove(it) } + } + } + } + + fun isComplete() = System.currentTimeMillis() > finishTimestamp +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/crafting/items/SavedCraftingItems.kt b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/items/SavedCraftingItems.kt new file mode 100644 index 00000000..72de04c2 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/items/SavedCraftingItems.kt @@ -0,0 +1,67 @@ +package dev.asodesu.islandutils.features.crafting.items + +import dev.asodesu.islandutils.api.extentions.islandUtilsFolder +import dev.asodesu.islandutils.api.json.decode +import dev.asodesu.islandutils.api.json.encode +import dev.asodesu.islandutils.api.modules.Module +import dev.asodesu.islandutils.features.crafting.CraftingMenuType +import java.nio.file.Path +import java.util.concurrent.CompletableFuture +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.minecraft.client.Minecraft +import kotlin.io.path.exists +import kotlin.io.path.readText +import kotlin.io.path.writeText + +object SavedCraftingItems : Module("CraftingItemsStorage") { + private val file: Path = islandUtilsFolder.resolve("crafting.json") + private var dirty = false + var items = mutableListOf() + + override fun init() { + this.load() + ClientTickEvents.END_CLIENT_TICK.register(::tick) + } + + fun tick(client: Minecraft) { + if (dirty) { + dirty = false + CompletableFuture.runAsync(::save) + } + } + + fun add(item: CraftingItem) { + remove(item.type, item.slot) + items += item + dirty = true + } + + fun remove(type: CraftingMenuType, slot: Int) { + items.removeIf { it.type == type && it.slot == slot } + dirty = true + } + + fun load() { + if (!file.exists()) return logger.info("No crafting items to load.") + val json = file.readText() + try { + items = json.decode>().toMutableList() + } catch (e: Exception) { + logger.error("Failed to load crafting items", e) + } + dirty = false + } + + fun save() { + val json = try { + items.encode() + } catch (e: Exception) { + logger.error("Failed to save crafting items", e) + return + } + + file.writeText(json) + logger.info("Saved crafting items") + dirty = false + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/crafting/notif/CraftingNotifier.kt b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/notif/CraftingNotifier.kt new file mode 100644 index 00000000..7f2feeac --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/notif/CraftingNotifier.kt @@ -0,0 +1,88 @@ +package dev.asodesu.islandutils.features.crafting.notif + +import dev.asodesu.islandutils.Font +import dev.asodesu.islandutils.Sounds +import dev.asodesu.islandutils.api.extentions.buildComponent +import dev.asodesu.islandutils.api.extentions.isOnline +import dev.asodesu.islandutils.api.extentions.minecraft +import dev.asodesu.islandutils.api.extentions.newLine +import dev.asodesu.islandutils.api.extentions.play +import dev.asodesu.islandutils.api.extentions.send +import dev.asodesu.islandutils.api.modules.Module +import dev.asodesu.islandutils.api.notifier.Notification +import dev.asodesu.islandutils.api.notifier.NotificationSource +import dev.asodesu.islandutils.api.notifier.Notifier +import dev.asodesu.islandutils.features.crafting.items.CraftingItem +import dev.asodesu.islandutils.features.crafting.items.SavedCraftingItems +import dev.asodesu.islandutils.options.NotificationOptions +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.minecraft.ChatFormatting +import net.minecraft.client.Minecraft +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.MutableComponent +import org.apache.commons.lang3.time.DurationFormatUtils + +object CraftingNotifier : Module("CraftingNotifier"), NotificationSource { + private var updateTicks = 0 + private val enabled by NotificationOptions.craftingNotifications + override val title: MutableComponent = Component.translatable("islandutils.notifications.crafting.complete") + + override fun init() { + SavedCraftingItems.init() + Notifier.add(this) + ClientTickEvents.END_CLIENT_TICK.register(::tick) + } + + fun tick(client: Minecraft) { + if (!enabled) return + if (!isOnline || client.overlay != null) return + if (++updateTicks < 20) return + + val toSend = SavedCraftingItems.items.filter { !it.notified && it.isComplete() } + if (toSend.isEmpty()) return + + toSend.forEach { + it.notified = true + client.toastManager.addToast(CraftingToast(it)) + send(chatNotification(it)) + } + minecraft.soundManager.play(Sounds.UI_ACHIVEMENT_RECEIVE) + SavedCraftingItems.save() + } + + private fun chatNotification(item: CraftingItem): Component { + return buildComponent { + withStyle { it.withColor(ChatFormatting.DARK_GREEN) } + + append(Component.literal("(").append(item.type.icon).append(") ")) // prefix + append(Component.translatable("islandutils.feature.crafting.chat.notification", item.item.hoverName)) + } + } + + fun commandResponse(): Component { + return buildComponent { + newLine() + append(Component.translatable("islandutils.feature.crafting.chat.list").withStyle(Font.HUD_STYLE)) + + SavedCraftingItems.items.forEach { item -> + val time = if (!item.isComplete()) { + val timeRemaining = item.finishTimestamp - System.currentTimeMillis() + val duration = DurationFormatUtils.formatDuration(timeRemaining, "H'h' m'm' s's'") + Component.literal(duration).withStyle(ChatFormatting.RED) + } else { + Component.translatable("islandutils.feature.crafting.chat.complete").withStyle(ChatFormatting.DARK_GREEN) + } + + newLine() + append(Component.translatable( + "islandutils.feature.crafting.chat.item", + item.type.icon, item.item.hoverName, time + )) + } + } + } + + override fun provide() = SavedCraftingItems.items.mapNotNull { + if (!it.isComplete()) null else Notification(it.item.hoverName, this) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/crafting/notif/CraftingToast.kt b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/notif/CraftingToast.kt new file mode 100644 index 00000000..4142395a --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/crafting/notif/CraftingToast.kt @@ -0,0 +1,46 @@ +package dev.asodesu.islandutils.features.crafting.notif + +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.features.crafting.items.CraftingItem +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.Font +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.toasts.Toast +import net.minecraft.client.gui.components.toasts.ToastManager +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.network.chat.Component +import net.minecraft.util.ARGB +import dev.asodesu.islandutils.Font as MccFont + +class CraftingToast(private val craftingItem: CraftingItem) : Toast { + private val BACKGROUND_SPRITE = Resources.islandUtils("toast/crafting_toast") + private val VISIBLE_FOR = 5000L + private val TITLE_TEXT = Component.translatable("islandutils.feature.crafting.complete") + .withStyle(MccFont.HUD_STYLE.withColor(ChatFormatting.WHITE)) + + private val TOP_PADDING = 7 + private val HUD_FONT_HEIGHT = 5 + private val TEXT_GAP = 4 + private val ITEM_HEIGHT = 16 + + private var visibility: Toast.Visibility = Toast.Visibility.HIDE + + override fun getWantedVisibility() = visibility + override fun update(toastManager: ToastManager, l: Long) { + val visibleFor = VISIBLE_FOR * toastManager.notificationDisplayTimeMultiplier + visibility = if (l > visibleFor) Toast.Visibility.HIDE else Toast.Visibility.SHOW + } + + override fun render(guiGraphics: GuiGraphics, font: Font, l: Long) { + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, BACKGROUND_SPRITE, 0, 0, this.width(), this.height()) + + var y = TOP_PADDING + guiGraphics.drawString(font, TITLE_TEXT, 30, y, ARGB.white(1f), false) + y += HUD_FONT_HEIGHT + TEXT_GAP + guiGraphics.drawString(font, craftingItem.item.hoverName, 30, y, ARGB.white(1f), false) + + guiGraphics.renderFakeItem(craftingItem.item, 8, 8) + guiGraphics.drawString(font, craftingItem.type.icon, 6, 8 + ITEM_HEIGHT - 6, ARGB.white(1f), false) + + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/CurrencyRenderer.kt b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/CurrencyRenderer.kt new file mode 100644 index 00000000..76902a7d --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/CurrencyRenderer.kt @@ -0,0 +1,46 @@ +package dev.asodesu.islandutils.features.scavenging + +import dev.asodesu.islandutils.api.chest.font.Icons +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.buildComponent +import dev.asodesu.islandutils.api.extentions.style +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component + +interface CurrencyRenderer { + fun apply(id: String): Boolean + fun render(total: Int): Component? + fun renderTooltip(total: Int): Component + + companion object { + fun formatTotal(total: Int): Component { + return Component.literal("%,d".format(total)).style { withColor(ChatFormatting.WHITE) } + } + + fun simpleIcon(icon: String) = Simple(Icons.getCharacter(Resources.mcc(icon))!!) + } + + class Unknown(private val currencyComponent: Component) : CurrencyRenderer { + override fun apply(id: String) = true + override fun render(total: Int) = null + override fun renderTooltip(total: Int) = buildComponent { + append(formatTotal(total)) + append(" ") + append(currencyComponent) + } + } + + class Simple(private val matchIcon: String, private val currencyComponent: Component = Icons.toIcon(matchIcon)) : CurrencyRenderer { + override fun apply(id: String) = id.endsWith(matchIcon) + + override fun render(total: Int): Component { + return buildComponent { + append(formatTotal(total)) + append(" ") + append(currencyComponent) + } + } + + override fun renderTooltip(total: Int) = render(total) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingCurrency.kt b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingCurrency.kt new file mode 100644 index 00000000..5a7a5b0e --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingCurrency.kt @@ -0,0 +1,3 @@ +package dev.asodesu.islandutils.features.scavenging + +class ScavengingCurrency(val total: Int, val renderer: CurrencyRenderer) \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingItem.kt b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingItem.kt new file mode 100644 index 00000000..597ce842 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingItem.kt @@ -0,0 +1,7 @@ +package dev.asodesu.islandutils.features.scavenging + +import net.minecraft.network.chat.Component + +class ScavengingItem(val rewards: MutableList = mutableListOf()) { + class Reward(val id: String, val component: Component, val total: Int) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingTotals.kt b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingTotals.kt new file mode 100644 index 00000000..b3b71c79 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/features/scavenging/ScavengingTotals.kt @@ -0,0 +1,180 @@ +package dev.asodesu.islandutils.features.scavenging + +import dev.asodesu.islandutils.Font +import dev.asodesu.islandutils.api.Messages +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyser +import dev.asodesu.islandutils.api.chest.analysis.ChestAnalyserFactory +import dev.asodesu.islandutils.api.chest.analysis.ContainerScreenHelper +import dev.asodesu.islandutils.api.chest.loreOrNull +import dev.asodesu.islandutils.api.extentions.Resources +import dev.asodesu.islandutils.api.extentions.buildComponent +import dev.asodesu.islandutils.api.extentions.debug +import dev.asodesu.islandutils.api.extentions.style +import dev.asodesu.islandutils.options.MiscOptions +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TextColor +import net.minecraft.resources.Identifier +import net.minecraft.world.item.ItemStack +import dev.asodesu.islandutils.api.extentions.minecraft as minecraftClient + +class ScavengingTotals : ChestAnalyser { + private val SLOTS_1 = 11..15 + private val SLOTS_2 = 20..25 + private val CONFIRM_BUTTON_SLOTS = 64..66 + + private val SCAVENGES_TITLE = "Scavenges Into" + private val SCAVENGING_TITLE_COLOR = TextColor.parseColor("#65FFFF").orThrow + private val CURRENCY_TOTAL_COLOR = TextColor.parseColor("#505050").orThrow + private val CURRENCY_TOTAL_REGEX = "(([\\d,])+)".toRegex() + + private val MCC_MENU_WIDTH = 176 + private val MCC_MENU_Y_OFFSET = -20 + private val MCC_HEADER_TO_FOOTER_HEIGHT = 154 + private val MCC_FOOTER_PADDING = 24 + + private val CHEST_MENU_HEIGHT = 222 + val minecraft = minecraftClient + + // currencies we render to the screen + private val CURRENCY_RENDERERS = listOf( + CurrencyRenderer.simpleIcon("_fonts/icon/silver.png"), + CurrencyRenderer.simpleIcon("_fonts/icon/coin_small.png"), + CurrencyRenderer.simpleIcon("_fonts/icon/material_dust.png"), + CurrencyRenderer.simpleIcon("_fonts/icon/royal_reputation.png") + ) + + private val scavengingItems = mutableMapOf() + private var currencies = listOf() + + private var totalsTooltip: List? = null + private var screenComponent: Component? = null + private var dirty = false + + override fun analyse(item: ItemStack, slot: Int) { + if (slot !in SLOTS_1 && slot !in SLOTS_2) return + val lores = item.loreOrNull + if (lores == null) { + scavengingItems.remove(slot) + dirty = true + return + } + + val scavengingItem = ScavengingItem() + var foundTitle = false + lores.forEach line@{ line -> + // first we need to find the "Scavenges Into:" text, if we haven't found it yet, all + // we're going to do is look for it. + if (!foundTitle) { + if (line.string.contains(SCAVENGES_TITLE)) { + // we have found Scavenges Into! whatever is below this line must be some + // tasty rewards + foundTitle = true + } + } else { + var amount: Int? = null + val currency = Component.empty() + + // search over all components in this lore line + line.siblings.forEach lineComponent@{ + // if we haven't found numbers for the amount yet, we need to find them + if (amount == null) { + // check for the currency total, being plain white text that matches CURRENCY_TOTAL_REGEX + if (it.style.color == null) { + // check if this component is JUST numbers + val result = CURRENCY_TOTAL_REGEX.find(it.string.trim()) ?: return@lineComponent + amount = result.groupValues[1].replace(",", "").toIntOrNull() ?: return@lineComponent + } + } else { + // we previously found an amount numbers, therefore everything after must be a + // space and then the currency icon + currency.append(it) + } + } + + // if we didn't find any numbers this line then we've probably found the end of the rewards for this item + if (amount == null) { + foundTitle = false + } else { + scavengingItem.rewards += ScavengingItem.Reward(currency.string, currency, amount!!) + } + } + } + scavengingItems[slot] = scavengingItem + dirty = true + } + + private fun updateScreenComponents() { + dirty = false + debug("Updating scavenging tooltip") + + if (scavengingItems.isEmpty()) { + totalsTooltip = null + screenComponent = null + return + } + + currencies = collect() + totalsTooltip = buildList { + add(Component.translatable("islandutils.feature.scavenging.manual")) + add(Component.empty()) + + add(Component.translatable("islandutils.feature.scavenging.into").style { withColor(SCAVENGING_TITLE_COLOR) }) + currencies.forEach { currency -> + val currencyTooltip = currency.renderer.renderTooltip(currency.total) + add( + Component.translatable("islandutils.feature.scavenging.reward", currencyTooltip) + .style { withColor(CURRENCY_TOTAL_COLOR) } + ) + } + add(Component.empty()) + add(Messages.action(Font.ACTION_CLICK_LEFT, "Click", "Scavenge Items")) + } + + screenComponent = buildComponent { + currencies.forEach { currency -> + val screenComponent = currency.renderer.render(currency.total) ?: return@forEach + append(" ") + append(screenComponent) + } + } + } + + private fun collect(): List { + val currencies = scavengingItems.flatMap { it.value.rewards }.groupBy { it.id } + return currencies.map { (id, items) -> + val renderer = CURRENCY_RENDERERS.firstOrNull { it.apply(id) } + ?: CurrencyRenderer.Unknown(items.first().component) + val total = items.sumOf { it.total } + ScavengingCurrency(total, renderer) + }.sortedBy { it.total } + } + + override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, helper: ContainerScreenHelper) { + if (dirty) updateScreenComponents() + val screen = helper.getScreen() + if (helper.getHoveredSlot()?.index in CONFIRM_BUTTON_SLOTS) { + totalsTooltip?.let { guiGraphics.setComponentTooltipForNextFrame(minecraft.font, it, mouseX, mouseY) } + } + + screenComponent?.let { component -> + val x = ((screen.width - MCC_MENU_WIDTH) / 2) + MCC_MENU_WIDTH - MCC_FOOTER_PADDING - minecraft.font.width(component) + + val footerContainerOffset = (MCC_FOOTER_PADDING - minecraft.font.lineHeight) / 2 + val y = ((screen.height - CHEST_MENU_HEIGHT) / 2) + MCC_MENU_Y_OFFSET + MCC_HEADER_TO_FOOTER_HEIGHT + footerContainerOffset + 1 + + guiGraphics.drawString(minecraft.font, component, x, y, -0x1, false) + } + } + + object Factory : ChestAnalyserFactory { + private val MENU_COMPONENT = Resources.mcc("_fonts/chest_backgrounds/body/scavenging.png") + private val enabled by MiscOptions.scavengingTotals + + override fun shouldApply(menuComponents: Collection): Boolean { + return enabled && menuComponents.contains(MENU_COMPONENT) + } + + override fun create(menuComponents: Collection) = ScavengingTotals() + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/BattleBox.kt b/src/main/kotlin/dev/asodesu/islandutils/games/BattleBox.kt new file mode 100644 index 00000000..f1c65913 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/BattleBox.kt @@ -0,0 +1,28 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.roundState +import dev.asodesu.islandutils.api.discord.layers.timeRemaining +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.SimpleGameContext + +class BattleBox(types: List) : Game("battle_box", types) { + override val hasTeamChat = false + override val discord = Discord { + images("battle_box") + playingDetails("Battle Box") + roundState() + timeRemaining() + } + + override fun toString() = "BattleBox" + + companion object : SimpleGameContext("battle_box") { + override fun create(packet: ClientboundMccServerPacket): Game { + return BattleBox(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/Dynaball.kt b/src/main/kotlin/dev/asodesu/islandutils/games/Dynaball.kt new file mode 100644 index 00000000..7eb09f23 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/Dynaball.kt @@ -0,0 +1,28 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playersRemainingState +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.timeElapsed +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.SimpleGameContext + +class Dynaball(types: List) : Game("dynaball", types) { + override val hasTeamChat = false + override val discord = Discord { + images("dynaball") + playingDetails("Dynaball") + playersRemainingState() + timeElapsed() + } + + override fun toString() = "Dynaball" + + companion object : SimpleGameContext("dynaball") { + override fun create(packet: ClientboundMccServerPacket): Game { + return Dynaball(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/Fishing.kt b/src/main/kotlin/dev/asodesu/islandutils/games/Fishing.kt new file mode 100644 index 00000000..b21e63c2 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/Fishing.kt @@ -0,0 +1,54 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.details +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.timeElapsed +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.GameContext +import dev.asodesu.islandutils.api.game.gameManager + +class Fishing(val island: String, types: List) : Game("fishing", types) { + override val hasTeamChat = false + override val discord = Discord { + images(island, "fishing") + details("Fishing in ${islandNames[island]}") + timeElapsed(gameManager.lastGameChange) + } + + override fun toString(): String { + return "Fishing(island=$island)" + } + + companion object : GameContext { + override val id: String = "fishing" + private val temperatures = listOf("temperate", "tropical", "barren") + val islandNames = mapOf( + "temperate_1" to "Verdant Woods", + "temperate_2" to "Floral Forest", + "temperate_3" to "Dark Grove", + "temperate_grotto" to "Sunken Swamp", + + "tropical_1" to "Tropical Overgrowth", + "tropical_2" to "Coral Shores", + "tropical_3" to "Twisted Swamp", + "tropical_grotto" to "Mirrored Oasis", + + "barren_1" to "Ancient Sands", + "barren_2" to "Blazing Canyon", + "barren_3" to "Ashen Wastes", + "barren_grotto" to "Volcanic Springs", + ) + + override fun check(packet: ClientboundMccServerPacket): Boolean { + return packet.server == "fishing" && temperatures.any { packet.types.any { type -> type.startsWith(it) } } + } + + override fun create(packet: ClientboundMccServerPacket): Game { + // find the first type in the list of server types that matches a valid island + // we can guarantee we have one, since we checked for it in the check function + return Fishing(packet.types.first { islandNames.containsKey(it) }, packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/HoleInTheWall.kt b/src/main/kotlin/dev/asodesu/islandutils/games/HoleInTheWall.kt new file mode 100644 index 00000000..fdb85f39 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/HoleInTheWall.kt @@ -0,0 +1,28 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playersRemainingState +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.timeRemaining +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.SimpleGameContext + +class HoleInTheWall(types: List) : Game("hole_in_the_wall", types) { + override val hasTeamChat = false + override val discord = Discord { + images("hitw") + playingDetails("Hole in the Wall") + playersRemainingState() + timeRemaining() + } + + override fun toString() = "HoleInTheWall" + + companion object : SimpleGameContext("hole_in_the_wall") { + override fun create(packet: ClientboundMccServerPacket): Game { + return HoleInTheWall(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/Hub.kt b/src/main/kotlin/dev/asodesu/islandutils/games/Hub.kt new file mode 100644 index 00000000..8ccc9546 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/Hub.kt @@ -0,0 +1,32 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.details +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.timeElapsed +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.GameContext +import dev.asodesu.islandutils.api.game.gameManager + +class Hub(types: List) : Game("lobby", types) { + override val hasTeamChat = false + override val discord = Discord { + images("hub") + details("In the Hub") + timeElapsed(gameManager.lastGameChange) + } + + override fun toString() = "Hub" + + companion object : GameContext { + override val id = "lobby" + override fun check(packet: ClientboundMccServerPacket): Boolean { + return packet.checkLobby() + } + + override fun create(packet: ClientboundMccServerPacket): Game { + return Hub(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/ParkourWarriorDojo.kt b/src/main/kotlin/dev/asodesu/islandutils/games/ParkourWarriorDojo.kt new file mode 100644 index 00000000..f8d79923 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/ParkourWarriorDojo.kt @@ -0,0 +1,48 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.state +import dev.asodesu.islandutils.api.discord.layers.timeElapsed +import dev.asodesu.islandutils.api.extentions.withValue +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.GameContext +import net.minecraft.network.chat.Component + +class ParkourWarriorDojo(val courseType: String, types: List) : Game("parkour_warrior_dojo", types) { + override val hasTeamChat = false + override val discord = Discord { + images("parkour_warrior_dojo") + playingDetails("Parkour Warrior: Dojo") + state { course } + timeElapsed() + } + + private val courseRegex = Regex("COURSE: (.+)") + var course: String? = null + + override fun onSidebarLine(component: Component) { + courseRegex.withValue(component) { + course = it + DiscordManager.update() + } + } + + override fun toString() = "ParkourWarriorDojo(course=$courseType)" + + companion object : GameContext { + override val id: String = "parkour_warrior_dojo" + override fun check(packet: ClientboundMccServerPacket): Boolean { + return packet.server == "dojo" && packet.checkTypes("parkour_warrior", "dojo") + } + + override fun create(packet: ClientboundMccServerPacket): Game { + val course = packet.types.firstOrNull { it.startsWith("main-") || it == "daily" } + ?: throw IllegalStateException("Couldn't find course from server types") + return ParkourWarriorDojo(course, packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/ParkourWarriorSurvivor.kt b/src/main/kotlin/dev/asodesu/islandutils/games/ParkourWarriorSurvivor.kt new file mode 100644 index 00000000..2cf4553b --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/ParkourWarriorSurvivor.kt @@ -0,0 +1,33 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.sidebarStateLayer +import dev.asodesu.islandutils.api.discord.layers.timeRemaining +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.GameContext + +class ParkourWarriorSurvivor(types: List) : Game("parkour_warrior_survivor", types) { + override val hasTeamChat = false + override val discord = Discord { + images("parkour_warrior_survivor") + playingDetails("Parkour Warrior: Survivor") + sidebarStateLayer("LEAP \\[(\\d+\\/\\d+)\\]", "Leap: ") + timeRemaining() + } + + override fun toString() = "ParkourWarriorSurvivor" + + companion object : GameContext { + override val id: String = "parkour_warrior_survivor" + override fun check(packet: ClientboundMccServerPacket): Boolean { + return packet.checkGame("parkour_warrior", "survivor") + } + + override fun create(packet: ClientboundMccServerPacket): Game { + return ParkourWarriorSurvivor(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/RocketSpleefRush.kt b/src/main/kotlin/dev/asodesu/islandutils/games/RocketSpleefRush.kt new file mode 100644 index 00000000..5954dfdf --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/RocketSpleefRush.kt @@ -0,0 +1,28 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playersRemainingState +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.timeRemaining +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.SimpleGameContext + +class RocketSpleefRush(types: List) : Game("rocket_spleef", types) { + override val hasTeamChat = false + override val discord = Discord { + images("rocket_spleef_rush") + playingDetails("Rocket Spleef Rush") + playersRemainingState() + timeRemaining() + } + + override fun toString() = "RocketSpleefRush" + + companion object : SimpleGameContext("rocket_spleef") { + override fun create(packet: ClientboundMccServerPacket): Game { + return RocketSpleefRush(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/SkyBattle.kt b/src/main/kotlin/dev/asodesu/islandutils/games/SkyBattle.kt new file mode 100644 index 00000000..c1b3d9e7 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/SkyBattle.kt @@ -0,0 +1,28 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playersRemainingState +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.timeRemaining +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.SimpleGameContext + +class SkyBattle(types: List) : Game("sky_battle", types) { + override val hasTeamChat = false + override val discord = Discord { + images("sky_battle") + playingDetails("Sky Battle") + playersRemainingState() + timeRemaining() + } + + override fun toString() = "SkyBattle" + + companion object : SimpleGameContext("sky_battle") { + override fun create(packet: ClientboundMccServerPacket): Game { + return SkyBattle(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/games/Tgttos.kt b/src/main/kotlin/dev/asodesu/islandutils/games/Tgttos.kt new file mode 100644 index 00000000..cb17ab15 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/games/Tgttos.kt @@ -0,0 +1,35 @@ +package dev.asodesu.islandutils.games + +import com.noxcrew.noxesium.core.mcc.ClientboundMccServerPacket +import dev.asodesu.islandutils.api.discord.Discord +import dev.asodesu.islandutils.api.discord.layers.images +import dev.asodesu.islandutils.api.discord.layers.playingDetails +import dev.asodesu.islandutils.api.discord.layers.roundState +import dev.asodesu.islandutils.api.discord.layers.timeRemaining +import dev.asodesu.islandutils.api.extentions.withValue +import dev.asodesu.islandutils.api.game.Game +import dev.asodesu.islandutils.api.game.context.SimpleGameContext +import net.minecraft.network.chat.Component + +class Tgttos(types: List) : Game("tgttos", types) { + override val hasTeamChat = false + override val discord = Discord { + images("tgttos") + playingDetails("TGTTOS") + roundState() + timeRemaining() + } + + private val modifierLineRegex = Regex("MODIFIER: ([\\w ]+)") + var modifier: String = "" + + override fun onSidebarLine(component: Component) { + modifierLineRegex.withValue(component) { modifier = it } + } + + companion object : SimpleGameContext("tgttos") { + override fun create(packet: ClientboundMccServerPacket): Game { + return Tgttos(packet.types) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/music/ClassicHitwMusic.kt b/src/main/kotlin/dev/asodesu/islandutils/music/ClassicHitwMusic.kt new file mode 100644 index 00000000..5025f49e --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/music/ClassicHitwMusic.kt @@ -0,0 +1,17 @@ +package dev.asodesu.islandutils.music + +import dev.asodesu.islandutils.api.music.MusicReplacementModifier +import dev.asodesu.islandutils.api.music.resources.RemoteResources +import dev.asodesu.islandutils.features.ClassicHitw +import dev.asodesu.islandutils.options.ClassicHitwOptions +import net.minecraft.resources.Identifier + +object ClassicHitwMusic : MusicReplacementModifier { + override val enableOption = ClassicHitwOptions.music + private const val MUSIC_KEY = "music.global.hole_in_the_wall" + private val REPLACEMENT_ASSET_KEY = RemoteResources.key(ClassicHitw.MUSIC_ASSET) + + override fun replace(server: Identifier): Identifier = REPLACEMENT_ASSET_KEY + override fun check(server: Identifier) = server.path == MUSIC_KEY + override fun downloadJob() = ClassicHitw.MUSIC_DOWNLOAD +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/music/HighQualityMusic.kt b/src/main/kotlin/dev/asodesu/islandutils/music/HighQualityMusic.kt new file mode 100644 index 00000000..370135ea --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/music/HighQualityMusic.kt @@ -0,0 +1,27 @@ +package dev.asodesu.islandutils.music + +import dev.asodesu.islandutils.api.music.MusicReplacementModifier +import dev.asodesu.islandutils.api.music.resources.RemoteResources +import dev.asodesu.islandutils.api.music.resources.handler.DownloadJob +import dev.asodesu.islandutils.options.MusicOptions +import net.minecraft.resources.Identifier + +object HighQualityMusic : MusicReplacementModifier { + override val enableOption by lazy { MusicOptions.Modifiers.highQualityMusic } // lazyload because options access download + private val musicOverrides = mutableMapOf( + "music.global.parkour_warrior" to "music/parkour_warrior", + "music.global.battle_box" to "music/battle_box", + "music.global.hole_in_the_wall" to "music/hole_in_the_wall", + "music.global.sky_battle" to "music/sky_battle", + "music.global.tgttosawaf" to "music/tgttos", + "music.global.rocket_spleef" to "music/rocket_spleef_rush", + "music.global.overtime_intro_music" to "music/overtime_intro_music", + "music.global.overtime_loop_music" to "music/overtime_loop_music", + "music.global.gameendmusic" to "music/game_end_music", + ) + val DOWNLOAD_JOB = DownloadJob.multi(musicOverrides.values) + + override fun replace(server: Identifier) = musicOverrides[server.path]?.let { RemoteResources.key(it) } + override fun check(server: Identifier) = musicOverrides.containsKey(server.path) + override fun downloadJob() = DOWNLOAD_JOB +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/music/OldDynaballMusic.kt b/src/main/kotlin/dev/asodesu/islandutils/music/OldDynaballMusic.kt new file mode 100644 index 00000000..2c944a69 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/music/OldDynaballMusic.kt @@ -0,0 +1,19 @@ +package dev.asodesu.islandutils.music + +import dev.asodesu.islandutils.api.music.MusicReplacementModifier +import dev.asodesu.islandutils.api.music.resources.RemoteResources +import dev.asodesu.islandutils.api.music.resources.handler.DownloadJob +import dev.asodesu.islandutils.options.MusicOptions +import net.minecraft.resources.Identifier + +object OldDynaballMusic : MusicReplacementModifier { + override val enableOption by lazy { MusicOptions.Modifiers.oldDynaball } // lazyload because options access download + private const val MUSIC_KEY = "music.global.dynaball" + private const val REPLACEMENT_ASSET = "old_dynaball/music" + val DOWNLOAD_JOB = DownloadJob.single(REPLACEMENT_ASSET) + private val REPLACEMENT_ASSET_KEY = RemoteResources.key(REPLACEMENT_ASSET) + + override fun replace(server: Identifier): Identifier = REPLACEMENT_ASSET_KEY + override fun check(server: Identifier) = server.path == MUSIC_KEY + override fun downloadJob() = DOWNLOAD_JOB +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/music/TgttosDomeMusic.kt b/src/main/kotlin/dev/asodesu/islandutils/music/TgttosDomeMusic.kt new file mode 100644 index 00000000..878e6519 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/music/TgttosDomeMusic.kt @@ -0,0 +1,23 @@ +package dev.asodesu.islandutils.music + +import dev.asodesu.islandutils.api.game.state.StateManager +import dev.asodesu.islandutils.api.music.MusicReplacementModifier +import dev.asodesu.islandutils.api.music.resources.RemoteResources +import dev.asodesu.islandutils.api.music.resources.handler.DownloadJob +import dev.asodesu.islandutils.options.MusicOptions +import net.minecraft.resources.Identifier + +object TgttosDomeMusic : MusicReplacementModifier { + override val enableOption by lazy { MusicOptions.Modifiers.tgttosDome } // lazyload because options access download + + private const val REPLACEMENT_ASSET = "music/to_the_dome" + private const val MUSIC_KEY = "music.global.tgttosawaf" // no fans on island :( + private val REPLACEMENT_ASSET_KEY = RemoteResources.key(REPLACEMENT_ASSET) + val DOWNLOAD_JOB = DownloadJob.single(REPLACEMENT_ASSET) + + override fun replace(server: Identifier): Identifier = REPLACEMENT_ASSET_KEY + override fun check(server: Identifier): Boolean { + return server.path == MUSIC_KEY && StateManager.current.mapId == "to_the_dome" + } + override fun downloadJob() = DOWNLOAD_JOB +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/music/TgttosDoubleTime.kt b/src/main/kotlin/dev/asodesu/islandutils/music/TgttosDoubleTime.kt new file mode 100644 index 00000000..02fbb86a --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/music/TgttosDoubleTime.kt @@ -0,0 +1,22 @@ +package dev.asodesu.islandutils.music + +import dev.asodesu.islandutils.api.events.sound.info.MutableSoundInfo +import dev.asodesu.islandutils.api.events.sound.info.SoundInfo +import dev.asodesu.islandutils.api.game.activeGame +import dev.asodesu.islandutils.api.music.MusicModifier +import dev.asodesu.islandutils.games.Tgttos +import dev.asodesu.islandutils.options.MusicOptions + +object TgttosDoubleTime : MusicModifier { + override val enableOption = MusicOptions.Modifiers.tgttosDoubleTime + + override fun modify(info: MutableSoundInfo) { + info.pitch = 1.2f + } + + override fun shouldApply(info: SoundInfo): Boolean { + // only apply during TGTTOS, when DOUBLE TIME is active + val tgttos = activeGame as? Tgttos ?: return false + return tgttos.modifier.startsWith("DOUBLE TIME", ignoreCase = true) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/options/ClassicHitwOptions.kt b/src/main/kotlin/dev/asodesu/islandutils/options/ClassicHitwOptions.kt new file mode 100644 index 00000000..de37a77d --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/options/ClassicHitwOptions.kt @@ -0,0 +1,10 @@ +package dev.asodesu.islandutils.options + +import dev.asodesu.islandutils.api.options.ConfigSection +import dev.asodesu.islandutils.api.options.download.withDownloadJob +import dev.asodesu.islandutils.features.ClassicHitw + +object ClassicHitwOptions : ConfigSection("section.classic_hitw") { + val annoucer = toggle("classic_hitw_annoucer", def = false, desc = true).withDownloadJob(ClassicHitw.ANNOUNCER_DOWNLOAD) + val music = toggle("classic_hitw_music", def = false, desc = true).withDownloadJob(ClassicHitw.MUSIC_DOWNLOAD) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/options/CosmeticOptions.kt b/src/main/kotlin/dev/asodesu/islandutils/options/CosmeticOptions.kt new file mode 100644 index 00000000..f88b4896 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/options/CosmeticOptions.kt @@ -0,0 +1,10 @@ +package dev.asodesu.islandutils.options + +import dev.asodesu.islandutils.api.options.ConfigSection + +object CosmeticOptions : ConfigSection("section.cosmetics") { + val enabled = toggle("cosmetics_enabled", def = true, desc = false) + val showOnHover = toggle("cosmetics_show_on_hover", def = true, desc = true) + val showInAllMenus = toggle("cosmetics_show_in_all_menus", def = false, desc = true) + val showInGames = toggle("cosmetics_show_in_games", def = true, desc = true) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/options/DiscordOptions.kt b/src/main/kotlin/dev/asodesu/islandutils/options/DiscordOptions.kt new file mode 100644 index 00000000..4696e328 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/options/DiscordOptions.kt @@ -0,0 +1,18 @@ +package dev.asodesu.islandutils.options + +import dev.asodesu.islandutils.api.discord.DiscordManager +import dev.asodesu.islandutils.api.extentions.isOnline +import dev.asodesu.islandutils.api.game.gameManager +import dev.asodesu.islandutils.api.options.ConfigSection +import dev.asodesu.islandutils.api.options.onChange +import dev.asodesu.islandutils.api.options.onDisabled +import dev.asodesu.islandutils.api.options.onEnable + +object DiscordOptions : ConfigSection("section.discord") { + val enabled = toggle("discord_enabled", def = true, desc = true) + .onEnable { if (isOnline) gameManager.pushDiscord() } + .onDisabled { DiscordManager.resetContainer() } + + val showPlace = toggle("discord_show_place", def = true, desc = true) + .onChange { if (isOnline) gameManager.pushDiscord() } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/options/MiscOptions.kt b/src/main/kotlin/dev/asodesu/islandutils/options/MiscOptions.kt new file mode 100644 index 00000000..b8fa4b85 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/options/MiscOptions.kt @@ -0,0 +1,22 @@ +package dev.asodesu.islandutils.options + +import dev.asodesu.islandutils.api.options.ConfigGroup +import dev.asodesu.islandutils.api.options.ConfigSection + +object MiscOptions : ConfigSection("section.misc") { + + val scavengingTotals = toggle("chest_scavenging_totals", desc = true, def = true) + val remnantHighlight = toggle("chest_remnant_highlight", desc = true, def = true) + val fishingUpgrades = toggle("chest_fishing_upgrade", desc = true, def = true) + val confirmDisconnect = toggle("confirm_disconnect", desc = true, def = true) + val chatChannelButtons = toggle("chat_channel_buttons", desc = true, def = true) + + object Debug : ConfigGroup("section.misc.debug") { + val debugMode = toggle(name = "debug_mode", desc = true, def = false) + } + + init { + group(Debug) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/options/MusicOptions.kt b/src/main/kotlin/dev/asodesu/islandutils/options/MusicOptions.kt new file mode 100644 index 00000000..b91a5636 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/options/MusicOptions.kt @@ -0,0 +1,23 @@ +package dev.asodesu.islandutils.options + +import dev.asodesu.islandutils.api.options.ConfigGroup +import dev.asodesu.islandutils.api.options.ConfigSection +import dev.asodesu.islandutils.api.options.download.withDownloadJob +import dev.asodesu.islandutils.music.HighQualityMusic +import dev.asodesu.islandutils.music.OldDynaballMusic +import dev.asodesu.islandutils.music.TgttosDomeMusic + +object MusicOptions : ConfigSection("section.music") { + val enabled = toggle("music_enabled", def = true, desc = true) + + object Modifiers : ConfigGroup("section.music.modifiers") { + val highQualityMusic = toggle("music_highq", def = true, desc = true).withDownloadJob(HighQualityMusic.DOWNLOAD_JOB) + val oldDynaball = toggle("music_olddynaball", def = false, desc = true).withDownloadJob(OldDynaballMusic.DOWNLOAD_JOB) + val tgttosDoubleTime = toggle("music_tgttosdouble", def = true, desc = true) + val tgttosDome = toggle("music_tgttosdome", def = true, desc = true).withDownloadJob(TgttosDomeMusic.DOWNLOAD_JOB) + } + + init { + group(Modifiers) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/options/NotificationOptions.kt b/src/main/kotlin/dev/asodesu/islandutils/options/NotificationOptions.kt new file mode 100644 index 00000000..078476c9 --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/options/NotificationOptions.kt @@ -0,0 +1,18 @@ +package dev.asodesu.islandutils.options + +import dev.asodesu.islandutils.api.options.ConfigGroup +import dev.asodesu.islandutils.api.options.ConfigSection + +object NotificationOptions : ConfigSection("section.notifs") { + val showInServerMenu = toggle("notifs_server_menu", def = true, desc = true) + val craftingNotifications = toggle("notifs_crafting", def = true, desc = true) + + object FriendsInGame : ConfigGroup("section.notifs.friendnotifs") { + val inGame = toggle(name = "friendnotifs_in_game", desc = true, def = true) + val inLobby = toggle(name = "friendnotifs_in_lobby", desc = true, def = true) + } + + init { + group(FriendsInGame) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/asodesu/islandutils/options/Options.kt b/src/main/kotlin/dev/asodesu/islandutils/options/Options.kt new file mode 100644 index 00000000..16c3423d --- /dev/null +++ b/src/main/kotlin/dev/asodesu/islandutils/options/Options.kt @@ -0,0 +1,15 @@ +package dev.asodesu.islandutils.options + +import dev.asodesu.islandutils.api.extentions.islandUtilsFolder +import dev.asodesu.islandutils.api.options.Config + +object Options : Config(islandUtilsFolder.resolve("islandutils.json").toFile()) { + override val entries = listOf( + MusicOptions, + CosmeticOptions, + DiscordOptions, + ClassicHitwOptions, + NotificationOptions, + MiscOptions + ) +} \ No newline at end of file diff --git a/src/main/resources/assets/island/font/icons.json b/src/main/resources/assets/island/font/icons.json deleted file mode 100644 index 11aa76a6..00000000 --- a/src/main/resources/assets/island/font/icons.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "providers": [ - { - "type":"space", - "advances":{" ": 4.0, "_": 2.0} - }, - { - "type": "bitmap", - "file": "mcc:island_interface/social/icon.png", - "chars": ["\ue001"], - "ascent": 7, - "height": 8 - }, - { - "type": "bitmap", - "file": "island:chat_channels/local.png", - "chars": ["\ue002"], - "ascent": 8, - "height": 9 - }, - { - "type": "bitmap", - "file": "island:chat_channels/party.png", - "chars": ["\ue003"], - "ascent": 8, - "height": 9 - }, - { - "type": "bitmap", - "file": "island:chat_channels/team.png", - "chars": ["\ue004"], - "ascent": 8, - "height": 9 - }, - { - "type": "bitmap", - "file": "island:middle_click.png", - "chars": ["\ue005"], - "ascent": 7, - "height": 7 - }, - { - "type": "bitmap", - "file": "mcc:_fonts/fusion_crafting.png", - "chars": ["\ue006"], - "ascent": 7, - "height": 16 - }, - { - "type": "bitmap", - "file": "mcc:_fonts/crafting.png", - "chars": ["\ue007"], - "ascent": 8, - "height": 16 - }, - { - "type": "bitmap", - "file": "island:chat_buttons/view.png", - "chars": ["\ue008"], - "ascent": 7, - "height": 7 - }, - { - "type": "bitmap", - "file": "mcc:_fonts/tick_small.png", - "chars": ["\ue009"], - "ascent": 8, - "height": 8 - }, - { - "type": "bitmap", - "file": "mcc:_fonts/cancel.png", - "chars": ["\ue010"], - "ascent": 8, - "height": 8 - }, - { - "type": "bitmap", - "file": "island:chat_channels/plobby.png", - "chars": ["\ue011"], - "ascent": 8, - "height": 9 - } - ] -} \ No newline at end of file diff --git a/src/main/resources/assets/island/lang/en_us.json b/src/main/resources/assets/island/lang/en_us.json deleted file mode 100644 index 3314a5dc..00000000 --- a/src/main/resources/assets/island/lang/en_us.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "menu.island_utils": "IslandUtils", - - "soundCategory.game_music": "§bMCCI: Game Music", - "soundCategory.core_music": "§cMCCI: Core Music", - "soundCategory.sound_effects": "§aMCCI: Sound Effects", - - "key.islandutils.plobbymenu": "Open Plobby Menu", - "key.islandutils.disguise": "Toggle Disguise", - "category.islandutils.keys": "IslandUtils", - "category.islandutils": "Island Utils", - - "text.autoconfig.islandutils.title": "Island Utils Options", - "text.autoconfig.islandutils.category.music": "Music", - "islandutils.music_modifier.tgttos.double_time": "TGTTOS Double Time Music", - "islandutils.music_modifier.tgttos.dome_modifier": "TGTTOS \"To The Dome!\" Music", - "islandutils.music_modifier.dynaball.old_music": "Previous Dynaball Music", - "islandutils.music_modifier.dynaball.old_music.desc": "Replaces Island's new Dynaball music with the music used previously.\n\n (NGS Live) Shoot 'em Up", - "islandutils.music_modifier.global.hq": "High(er) Quality Music", - "islandutils.music_modifier.global.hq.desc": "Replaces Island's downloaded music with high(er) quality versions (does not apply to Hub Music & Dynaball)", - - "text.autoconfig.islandutils.category.cosmetics": "Cosmetic Previews", - "text.autoconfig.islandutils.option.showPlayerPreview": "Show Cosmetic Previews", - "text.autoconfig.islandutils.option.showOnHover": "Preview cosmetics on hover", - "text.autoconfig.islandutils.option.showOnOnlyCosmeticMenus": "Show cosmetic preview only in Cosmetic Menus", - - "text.autoconfig.islandutils.category.discord": "Discord Presence", - "text.autoconfig.islandutils.option.discordPresence": "Enable Discord Presence", - "text.autoconfig.islandutils.option.showGame": "Show Game", - "text.autoconfig.islandutils.option.showGame.@Tooltip": "Show the game that you're playing", - "text.autoconfig.islandutils.option.showGameInfo": "Show Game Details", - "text.autoconfig.islandutils.option.showGameInfo.@Tooltip": "Show the Details of your current game (e.g. Round, Players Remaining)", - "text.autoconfig.islandutils.option.showTimeRemaining": "Show Time Remaining", - "text.autoconfig.islandutils.option.showTimeRemaining.@Tooltip": "Show how long is left in your current game\n(Disabling will always show time elapsed)", - "text.autoconfig.islandutils.option.showTimeElapsed": "Show Time Elapsed", - "text.autoconfig.islandutils.option.showTimeElapsed.@Tooltip": "Show how long you've been playing on island!", - "text.autoconfig.islandutils.option.showFactionLevel": "Show Faction Level", - "text.autoconfig.islandutils.option.showFactionLevel.@Tooltip": "Show your current Faction and Level.", - - "text.autoconfig.islandutils.category.crafting_notfis": "Crafting Notifications", - "text.autoconfig.islandutils.option.enableCraftingNotifs": "Enable Crafting Notifications", - "text.autoconfig.islandutils.option.toastNotif": "Enable popup notification", - "text.autoconfig.islandutils.option.toastNotif.@Tooltip": "Adds a pop-up in the top right when your items have finished crafting\n\n§e§l[Only shows if you are playing on MCCI]", - "text.autoconfig.islandutils.option.chatNotif": "Enable chat notification", - "text.autoconfig.islandutils.option.chatNotif.@Tooltip": "Adds a chat message when your items have finished crafting", - "text.autoconfig.islandutils.option.notifyServerList": "Add server list notification", - "text.autoconfig.islandutils.option.notifyServerList.@Tooltip": "Adds an icon to the server list when your items have finished crafting.", - - "text.autoconfig.islandutils.category.pkw_splits": "Parkour Warrior Splits", - "text.autoconfig.islandutils.option.enablePkwSplits": "Enable PKW: Dojo time splits", - "text.autoconfig.islandutils.option.sendSplitTime": "Send segment times in chat", - "text.autoconfig.islandutils.option.sendSplitTime.@Tooltip": "Sends segment in chat after you complete a level", - "text.autoconfig.islandutils.option.showTimer": "Show timer bar", - "text.autoconfig.islandutils.option.showTimer.@Tooltip": "Enables the timer bar which shows your current segment time and your improvement over a saved segment", - "text.autoconfig.islandutils.option.showSplitImprovements": "Show segment improvements", - "text.autoconfig.islandutils.option.showSplitImprovements.@Tooltip": "Shows your improvement over a saved segment in the timer bar, medal title and in chat", - "text.autoconfig.islandutils.option.showTimerImprovementAt": "Show segment improvement at", - "text.autoconfig.islandutils.option.showTimerImprovementAt.@Tooltip": "At what time should the segment improvement time start showing in the timer bar", - "text.autoconfig.islandutils.option.clearSplits": "Clear Saved Splits", - "text.autoconfig.islandutils.option.clearSplits.@Tooltip": "This will clear all of your saved segments. These can not be recovered.", - "text.autoconfig.islandutils.option.saveMode": "Split save mode", - "text.autoconfig.islandutils.option.saveMode.@Tooltip": "How to save splits\n\n§lBest Time§r: Uses your best time on each segment\n\n§lAverage Time§r: Uses your average time on a segment", - "islandutils.splittype.best": "Best Time", - "islandutils.splittype.avg": "Average Time", - - "text.autoconfig.islandutils.category.misc": "Miscellaneous", - "text.autoconfig.islandutils.option.pauseConfirm": "Confirm Before Disconnect", - "text.autoconfig.islandutils.option.pauseConfirm.@Tooltip": "Adds an extra confirmation screen before you disconnect", - "text.autoconfig.islandutils.option.showFriendsInGame": "Show which friends are in your game", - "text.autoconfig.islandutils.option.showFriendsInGame.@Tooltip": "When you join a game, a message will be put in chat with the friends in your game", - "text.autoconfig.islandutils.option.showFriendsInLobby": "Show which friends are in your lobby", - "text.autoconfig.islandutils.option.showFriendsInLobby.@Tooltip": "When you join or change lobbies, a message will be put in chat with the friends in your lobby", - "text.autoconfig.islandutils.option.silverPreview": "Show scavenging item totals", - "text.autoconfig.islandutils.option.silverPreview.@Tooltip": "Shows all the items you'll earn in the Scavenging menu", - "text.autoconfig.islandutils.option.channelSwitchers": "Show chat channel buttons", - "text.autoconfig.islandutils.option.channelSwitchers.@Tooltip": "Shows the chat channel switcher buttons above the chat box", - "text.autoconfig.islandutils.option.hideSliders": "Hide Sound Sliders if not Online", - "text.autoconfig.islandutils.option.hideSliders.@Tooltip": "Hides the Music Sliders if you're not on MCC Island", - "text.autoconfig.islandutils.option.showFishingUpgradeIcon": "Show fishing upgrade icons", - - "text.autoconfig.islandutils.category.classic_hitw": "Classic HITW", - "text.autoconfig.islandutils.option.classicHITW": "Classic Hole In The Wall Announcer", - "text.autoconfig.islandutils.option.classicHITW.@Tooltip": "Adds back the announcer from the original HITW map by Noxcrew!", - "text.autoconfig.islandutils.option.classicHITWMusic": "Classic Hole In The Wall Music", - "text.autoconfig.islandutils.option.classicHITWMusic.@Tooltip": "Adds back the music from the original HITW map by Noxcrew!", - "text.autoconfig.islandutils.option.enableConfigButton": "Enable Config Button in options", - "text.autoconfig.islandutils.option.enableConfigButton.@Tooltip": "Enables the Config Button in the top left of the options menu\n\n§c§lWARNING! §rIf you disable this and you don't have another way to access the config (ModMenu), you will be unable to access the config in-game!", - - "text.autoconfig.islandutils.option.showOnScreen": "Show on-screen code", - "text.autoconfig.islandutils.option.showOnScreen.@Tooltip": "Shows the plobby join code on-screen", - "text.autoconfig.islandutils.option.showInGame": "Show in games", - "text.autoconfig.islandutils.option.showInGame.@Tooltip": "Shows the lobby code while in games", - - "text.autoconfig.islandutils.option.showFolder": "Open text source folder", - "text.autoconfig.islandutils.option.showFolder.@Tooltip": "Opens the text sources folder\n\nContents:\ncode.txt -> Auto-updates with the current plobby join code", - - "text.autoconfig.islandutils.option.debugMode": "Debug Mode", - "text.autoconfig.islandutils.option.debugMode.@Tooltip": "Enables: Chat debug logs, Custom Item ID tooltips & Other debug features", - - "modmenu.descriptionTranslation.islandutils": "A Utility Mod for MCC Island!", - "modmenu.summaryTranslation.islandutils": "A Utility Mod for MCC Island!" -} diff --git a/src/main/resources/assets/island/lang/fi_fi.json b/src/main/resources/assets/island/lang/fi_fi.json deleted file mode 100644 index 1ad47431..00000000 --- a/src/main/resources/assets/island/lang/fi_fi.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "menu.island_utils": "IslandUtils", - - "soundCategory.game_music": "§bMCCI: Pelimusiikki", - "soundCategory.core_music": "§cMCCI: Perusmusiikki", - "soundCategory.sound_effects": "§aMCCI: Äänitehosteet", - - "key.islandutils.preview": "Esikatsele Kosmetiikka", - "category.islandutils.keys": "IslandUtils", - "category.islandutils": "Island Utils", - - "text.autoconfig.islandutils.title": "Island Utils- Asetukset", - "text.autoconfig.islandutils.category.music": "Musiikki", - "text.autoconfig.islandutils.option.tgttosMusic": "TGTTOS- Musiikki", - "text.autoconfig.islandutils.option.hitwMusic": "HITW- Musiikki", - "text.autoconfig.islandutils.option.bbMusic": "Battle Box- Musiikki", - "text.autoconfig.islandutils.option.sbMusic": "Sky Battle- Musiikki", - "text.autoconfig.islandutils.option.pkwMusic": "Parkour Warrior Music", - "text.autoconfig.islandutils.option.tgttosDoubleTime": "TGTTOS Tupla-aika- Musiikki", - - "text.autoconfig.islandutils.category.cosmetics": "Kosmetiikka", - "text.autoconfig.islandutils.option.showPlayerPreview": "Näytä Kosmetiikkojen Esikatselut", - "text.autoconfig.islandutils.option.showOnHover": "Päivitä esikatselu osoittaessa hiirellä", - "text.autoconfig.islandutils.option.showOnOnlyCosmeticMenus": "Näytä kosmeettinen esikatselu ainoastaan Kosmetiikka-menussa", - - "text.autoconfig.islandutils.category.discord": "Discord-tila", - "text.autoconfig.islandutils.option.discordPresence": "Ota Käyttöön Discord-tila", - "text.autoconfig.islandutils.option.showGame": "Näytä peli", - "text.autoconfig.islandutils.option.showGame.@Tooltip": "Näytä nykyinen peli", - "text.autoconfig.islandutils.option.showGameInfo": "Näytä pelin tiedot", - "text.autoconfig.islandutils.option.showGameInfo.@Tooltip": "Näytä nykyisen pelin tiedot (esim. Kierros, Pelaajia jäljellä)", - "text.autoconfig.islandutils.option.showTimeRemaining": "Näytä jäljellä oleva aika", - "text.autoconfig.islandutils.option.showTimeRemaining.@Tooltip": "Näytä kuinka paljon aikaa nykyisessä pelissä on jäljellä\n(Poistaminen käytöstä näyttää aina kuluneen ajan)", - "text.autoconfig.islandutils.option.showTimeElapsed": "Näytä kulunut aika", - "text.autoconfig.islandutils.option.showTimeElapsed.@Tooltip": "Näytä kuinka kauan olet pelannut Islandilla!", - "text.autoconfig.islandutils.option.showFactionLevel": "Näytä Faction-taso", - "text.autoconfig.islandutils.option.showFactionLevel.@Tooltip": "Näytä nykyinen Faction ja taso.", - - "text.autoconfig.islandutils.category.misc": "Sekalainen", - "text.autoconfig.islandutils.option.pauseConfirm": "Vahvista ennen yhteyden katkaisemista", - "text.autoconfig.islandutils.option.showFriendsInGame": "Näytä kaverit pelissäsi", - "text.autoconfig.islandutils.option.showFriendsInGame.@Tooltip": "Kun liityt peliin, chattiin tulee viesti joka näyttää mitkä kaverit ovat pelissäsi.", - "text.autoconfig.islandutils.option.hideSliders": "Piilota ääniasetukset Islandin ulkopuolella", - "text.autoconfig.islandutils.option.hideSliders.@Tooltip": "Piilottaa ääniasetukset kun et ole MCC Islandilla.", - - "text.autoconfig.islandutils.category.classic_hitw": "Klassinen HITW", - "text.autoconfig.islandutils.option.classicHITW": "Klassinen Hole In The Wall- Kuuluttaja", - "text.autoconfig.islandutils.option.classicHITW.@Tooltip": "Lisää takaisin kuuluttajan \nNoxcrewin alkuperäisestä HITW-kartasta!", - "text.autoconfig.islandutils.option.classicHITWMusic": "Klassinen Hole In The Wall- Musiikki", - "text.autoconfig.islandutils.option.classicHITWMusic.@Tooltip": "Lisää takaisin musiikin \nNoxcrewin alkuperäisestä HITW-kartasta!", - "text.autoconfig.islandutils.option.enableConfigButton": "Lisää konfiguraationappi asetusvalikkoon", - "text.autoconfig.islandutils.option.enableConfigButton.@Tooltip": "Lisää konfiguraationapin asetusvalikon vasempaan kulmaan\n§c§VAROITUS! §rJos poistat tämän käytöstä ja sinulla ei ole vaihtoehtoista menetelmää päästä asetuksiin (ModMenu), et voi enää muuttaa asetuksia pelinsisäisesti!", - - - "text.autoconfig.islandutils.option.debugMode": "Debug-Tila", - - "modmenu.descriptionTranslation.islandutils": "Käytännöllisyys-Modi MCC Islandille!", - "modmenu.summaryTranslation.islandutils": "Käytännöllisyys-Modi MCC Islandille!" -} \ No newline at end of file diff --git a/src/main/resources/assets/island/lang/hu_hu.json b/src/main/resources/assets/island/lang/hu_hu.json deleted file mode 100644 index 497b4dea..00000000 --- a/src/main/resources/assets/island/lang/hu_hu.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "menu.island_utils": "IslandUtils", - - "soundCategory.game_music": "§bMCCI: Zene", - "soundCategory.core_music": "§cMCCI: Alapzene", - "soundCategory.sound_effects": "§aMCCI: Hangeffektek", - - "key.islandutils.preview": "Kozmetikai Előnézet", - "category.islandutils.keys": "IslandUtils", - "category.islandutils": "Island Utils", - - "text.autoconfig.islandutils.title": "Island Utils Beállítások", - "text.autoconfig.islandutils.category.music": "Zene", - "text.autoconfig.islandutils.option.tgttosMusic": "TGTTOS Zene", - "text.autoconfig.islandutils.option.hitwMusic": "HITW Zene", - "text.autoconfig.islandutils.option.bbMusic": "Battle Box Zene", - "text.autoconfig.islandutils.option.sbMusic": "Sky Battle Zene", - "text.autoconfig.islandutils.option.tgttosDoubleTime": "TGTTOS Dupla Idő Zene", - - "text.autoconfig.islandutils.category.cosmetics": "Kozmetikai Előnézetek", - "text.autoconfig.islandutils.option.showPlayerPreview": "Kozmetikai Előnézetek Mutatása", - "text.autoconfig.islandutils.option.showOnHover": "Előnézet Frissítése Rámutatáskor", - "text.autoconfig.islandutils.option.showOnOnlyCosmeticMenus": "A kozmetikai előnézet megjelenítése csak kozmetikai menükben", - - "text.autoconfig.islandutils.category.discord": "Discord Státusz", - "text.autoconfig.islandutils.option.discordPresence": "Discord Státusz Bekapcsolása", - "text.autoconfig.islandutils.option.showGame": "Játék mutatása", - "text.autoconfig.islandutils.option.showGame.@Tooltip": "Mutasd meg a játékot, amivel játszol", - "text.autoconfig.islandutils.option.showGameInfo": "A játék részleteinek megjelenítése", - "text.autoconfig.islandutils.option.showGameInfo.@Tooltip": "Az aktuális játék részleteinek megjelenítése (pl. forduló, hátralévő játékosok)", - "text.autoconfig.islandutils.option.showTimeRemaining": "Hátralévő idő Mutatása", - "text.autoconfig.islandutils.option.showTimeRemaining.@Tooltip": "Megmutatja, mennyi idő van még hátra az aktuális játékból\n(kikapcsolva mindig az eltelt időt mutatja)", - "text.autoconfig.islandutils.option.showTimeElapsed": "Eltelt idő Megjelenítése", - "text.autoconfig.islandutils.option.showTimeElapsed.@Tooltip": "Mutasd meg, mióta játszol a szigeten!", - "text.autoconfig.islandutils.option.showFactionLevel": "Faction Szint Mutatása", - "text.autoconfig.islandutils.option.showFactionLevel.@Tooltip": "Megmutatja a Factiont amiben vagy és a szinted.", - - "text.autoconfig.islandutils.category.misc": "Egyéb", - "text.autoconfig.islandutils.option.pauseConfirm": "Kilépés Megerősítése", - "text.autoconfig.islandutils.option.showFriendsInGame": "Velem egy ugyanazon játékban lévő barátok mutatása", - "text.autoconfig.islandutils.option.showFriendsInGame.@Tooltip": "Amikor csatlakozol egy játékhoz, egy üzenet jelenik meg a chatben a játékban lévő barátaiddal.", - "text.autoconfig.islandutils.option.hideSliders": "Hang csúszkák elrejtése, ha nem vagy Online", - "text.autoconfig.islandutils.option.hideSliders.@Tooltip": "Elrejti a Zene csúszkákat, ha nem az MCC Islanden vagy.", - - "text.autoconfig.islandutils.category.classic_hitw": "Klasszikus HITW", - "text.autoconfig.islandutils.option.classicHITW": "Klasszikus Hole In The Wall Bemondó", - "text.autoconfig.islandutils.option.classicHITW.@Tooltip": "Visszaadja a Bemondót ahogyan az a \nHivatalos HITW mapen is volt amit a Noxcrew készített!", - "text.autoconfig.islandutils.option.classicHITWMusic": "Klasszikus Hole In The Wall Zene", - "text.autoconfig.islandutils.option.classicHITWMusic.@Tooltip": "Visszaadja a Zenét ahogyan az a \nHivatalos HITW mapen is volt amit a Noxcrew készített!", - - "text.autoconfig.islandutils.option.debugMode": "Debug Mód", - - "modmenu.descriptionTranslation.islandutils": "Egy Utility Mod az MCC Islandhez!", - "modmenu.summaryTranslation.islandutils": "Egy Utility Mod az MCC Islandhez!" -} diff --git a/src/main/resources/assets/island/textures/chat_buttons/view.png b/src/main/resources/assets/island/textures/chat_buttons/view.png deleted file mode 100644 index 66e92962..00000000 Binary files a/src/main/resources/assets/island/textures/chat_buttons/view.png and /dev/null differ diff --git a/src/main/resources/assets/island/textures/gui/sprites/pkw_splits.png b/src/main/resources/assets/island/textures/gui/sprites/pkw_splits.png deleted file mode 100644 index 41e42d32..00000000 Binary files a/src/main/resources/assets/island/textures/gui/sprites/pkw_splits.png and /dev/null differ diff --git a/src/main/resources/assets/island/textures/gui/sprites/toasts.png b/src/main/resources/assets/island/textures/gui/sprites/toasts.png deleted file mode 100644 index 65924c49..00000000 Binary files a/src/main/resources/assets/island/textures/gui/sprites/toasts.png and /dev/null differ diff --git a/src/main/resources/assets/island/textures/middle_click.png b/src/main/resources/assets/island/textures/middle_click.png deleted file mode 100644 index 0f3a4d8a..00000000 Binary files a/src/main/resources/assets/island/textures/middle_click.png and /dev/null differ diff --git a/src/main/resources/assets/islandutils/font/icons.json b/src/main/resources/assets/islandutils/font/icons.json new file mode 100644 index 00000000..ce2222f5 --- /dev/null +++ b/src/main/resources/assets/islandutils/font/icons.json @@ -0,0 +1,13 @@ +{ + "providers": [ + {"type":"bitmap","file": "mcc:island_interface/social/icon.png","chars": ["\ue001"],"ascent":8,"height":8}, + {"type":"bitmap","file": "mcc:_fonts/header_icons/fusion_crafting.png","chars": ["\ue002"],"ascent":8,"height":16}, + {"type":"bitmap","file": "mcc:_fonts/header_icons/crafting.png","chars": ["\ue003"],"ascent":8,"height":16}, + {"type":"bitmap","file": "mcc:_fonts/icon/click_action_left.png","chars": ["\ue004"],"ascent":8,"height":8}, + {"type":"bitmap","file": "mcc:_fonts/icon/click_action_right.png","chars": ["\ue005"],"ascent":8,"height":8}, + {"type":"bitmap","file": "mcc:_fonts/icon/tooltips/hat.png","chars": ["\ue006"],"ascent":7,"height":9}, + {"type":"bitmap","file": "mcc:_fonts/icon/tooltips/accessory.png","chars": ["\ue007"],"ascent":7,"height":9}, + {"type":"bitmap","file": "mcc:_fonts/icon/tooltips/back.png","chars": ["\ue008"],"ascent":7,"height":9}, + {"type":"bitmap","file": "mcc:_fonts/icon/tooltips/fishing_rod.png","chars": ["\ue009"],"ascent":7,"height":9} + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/island/icon.png b/src/main/resources/assets/islandutils/icon.png similarity index 100% rename from src/main/resources/assets/island/icon.png rename to src/main/resources/assets/islandutils/icon.png diff --git a/src/main/resources/assets/islandutils/lang/en_us.json b/src/main/resources/assets/islandutils/lang/en_us.json new file mode 100644 index 00000000..11e38b08 --- /dev/null +++ b/src/main/resources/assets/islandutils/lang/en_us.json @@ -0,0 +1,80 @@ +{ + "menu.islandutils": "IslandUtils", + "islandutils.feature.friends.game": "Friends in this game", + "islandutils.feature.friends.lobby": "Friends in this lobby", + "islandutils.feature.crafting.chat.list": "CRAFTING ITEMS:", + "islandutils.feature.crafting.chat.item": "%s %s - %s", + "islandutils.feature.crafting.chat.complete": "Complete", + "islandutils.feature.crafting.chat.notification": "%s has finished crafting!", + "islandutils.feature.crafting.complete": "CRAFTING COMPLETE", + + "islandutils.feature.scavenging.into": "You will earn:", + "islandutils.feature.scavenging.reward": " ▪ %s", + "islandutils.feature.scavenging.manual": "Manual Scavenge", + + "islandutils.confirm_disconnect.title": "Are you sure you want to disconnect?", + "islandutils.confirm_disconnect.disconnect": "Leave The Server", + + "islandutils.notifications.title": "Notifications", + "islandutils.notifications.crafting.complete": "Completed Crafts:", + + "islandutils.options.section.music": "Music", + "islandutils.options.music_enabled": "Enable Music Features", + "islandutils.options.music_enabled.desc": "Enables music modification features\n\nIf you're having issues with Island's music, try disabling this option", + "islandutils.options.section.music.modifiers": "Music Modifiers", + "islandutils.options.music_highq": "Higher quality music", + "islandutils.options.music_highq.desc": "Enables Higher quality music, this replaces the music on island with higher quality versions.\n\nNote: This does not apply to Hub music or Dynaball", + "islandutils.options.music_olddynaball": "Legacy Dynaball music", + "islandutils.options.music_olddynaball.desc": "Replaces Island's Dynaball music with 'Shoot 'em Up (NGS Live)' by Issac Wilkins\n\n(The music previously used by IslandUtils before the official music)", + "islandutils.options.music_tgttosdouble": "TGTTOS: Double time", + "islandutils.options.music_tgttosdouble.desc": "Speeds up the music when the TGTTOS Double Time modifier is active", + "islandutils.options.music_tgttosdome": "TGTTOS: To The Dome!", + "islandutils.options.music_tgttosdome.desc": "Replaces the TGTTOS music with 'One Minute to MCC' during the 'To The Dome!' map", + + "islandutils.options.section.cosmetics": "Cosmetic Previews", + "islandutils.options.cosmetics_enabled": "Enable Cosmetic Previews", + "islandutils.options.cosmetics_show_on_hover": "Show hovered cosmetics on player", + "islandutils.options.cosmetics_show_on_hover.desc": "Updated the cosmetic preview with the item you're hovering over", + "islandutils.options.cosmetics_show_in_all_menus": "Show in all menus", + "islandutils.options.cosmetics_show_in_all_menus.desc": "Shows the cosmetic preview player in all menus instead of only menus containing cosmetic items", + "islandutils.options.cosmetics_show_in_games": "Show in games", + "islandutils.options.cosmetics_show_in_games.desc": "Shows cosmetic preview in games", + + "islandutils.options.section.discord": "Discord", + + "islandutils.options.section.classic_hitw": "Classic HITW", + "islandutils.options.classic_hitw_annoucer": "Enable trap annoucer voice lines", + "islandutils.options.classic_hitw_annoucer.desc": "Enables the trap annoucement voice lines from the original 'Super Hole In The Wall' map", + "islandutils.options.classic_hitw_music": "Enable Classic HITW music", + "islandutils.options.classic_hitw_music.desc": "Replaces the Hole In The Wall music with the music from the original 'Super Hole In The Wall' map", + + "islandutils.options.section.notifs": "Notifications", + "islandutils.options.notifs_server_menu": "Show notifications in server menu", + "islandutils.options.notifs_server_menu.desc": "Enables showing notifications next to MCC Island in the server menu", + "islandutils.options.notifs_crafting": "Show crafting notifications", + "islandutils.options.notifs_crafting.desc": "Enables notifications for your completed Blueprint Assembler or Fusion Forge items", + "islandutils.options.section.notifs.friendnotifs": "Friend Notifications", + "islandutils.options.friendnotifs_in_game": "Send friend notifications in games", + "islandutils.options.friendnotifs_in_game.desc": "Enables sending 'Friends in this game' notifications", + "islandutils.options.friendnotifs_in_lobby": "Send friend notifications in lobbies", + "islandutils.options.friendnotifs_in_lobby.desc": "Enables sending 'Friends in this lobby' notifications", + + "islandutils.options.section.misc": "Miscellaneous", + "islandutils.options.chest_scavenging_totals": "Show scavenging totals", + "islandutils.options.chest_scavenging_totals.desc": "Enables showing your total rewards when scavenging items. These are shown in the menu and when hovering over the Confirm button", + "islandutils.options.chest_remnant_highlight": "Highlight item remnants", + "islandutils.options.chest_remnant_highlight.desc": "Enables a red background behind item remnants which can be scavenged", + "islandutils.options.chest_fishing_upgrade": "Highlight available fishing upgrades", + "islandutils.options.chest_fishing_upgrade.desc": "Enables icons on available fishing upgrades", + "islandutils.options.confirm_disconnect": "Enable confirm when disconnecting", + "islandutils.options.confirm_disconnect.desc": "Enables a confirmation screen when disconnecting", + "islandutils.options.chat_channel_buttons": "Show chat channel switcher", + "islandutils.options.chat_channel_buttons.desc": "Enabled buttons in chat to switch chat channels", + "islandutils.options.section.misc.debug": "Debug Options", + "islandutils.options.debug_mode": "Debug Mode", + "islandutils.options.debug_mode.desc": "Enables Debugging Options", + + "key.category.islandutils": "IslandUtils", + "key.islandutils.disguise": "Toggle Disguise", + "key.islandutils.plobbymenu": "Open Plobby Menu" +} \ No newline at end of file diff --git a/src/main/resources/assets/island/textures/chat_channels/local.png b/src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/local.png similarity index 100% rename from src/main/resources/assets/island/textures/chat_channels/local.png rename to src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/local.png diff --git a/src/main/resources/assets/island/textures/chat_channels/party.png b/src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/party.png similarity index 100% rename from src/main/resources/assets/island/textures/chat_channels/party.png rename to src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/party.png diff --git a/src/main/resources/assets/island/textures/chat_channels/plobby.png b/src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/plobby.png similarity index 100% rename from src/main/resources/assets/island/textures/chat_channels/plobby.png rename to src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/plobby.png diff --git a/src/main/resources/assets/island/textures/chat_channels/team.png b/src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/team.png similarity index 100% rename from src/main/resources/assets/island/textures/chat_channels/team.png rename to src/main/resources/assets/islandutils/textures/gui/sprites/chat_button/team.png diff --git a/src/main/resources/assets/island/textures/gui/sprites/preview.png b/src/main/resources/assets/islandutils/textures/gui/sprites/preview.png similarity index 100% rename from src/main/resources/assets/island/textures/gui/sprites/preview.png rename to src/main/resources/assets/islandutils/textures/gui/sprites/preview.png diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/toast/crafting_toast.png b/src/main/resources/assets/islandutils/textures/gui/sprites/toast/crafting_toast.png new file mode 100644 index 00000000..9c0cc847 Binary files /dev/null and b/src/main/resources/assets/islandutils/textures/gui/sprites/toast/crafting_toast.png differ diff --git a/src/main/resources/assets/island/textures/gui/sprites/upgrade.png b/src/main/resources/assets/islandutils/textures/gui/sprites/upgrade.png similarity index 100% rename from src/main/resources/assets/island/textures/gui/sprites/upgrade.png rename to src/main/resources/assets/islandutils/textures/gui/sprites/upgrade.png diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/all.png b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/all.png new file mode 100644 index 00000000..a92f268a Binary files /dev/null and b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/all.png differ diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/all.png.mcmeta b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/all.png.mcmeta new file mode 100644 index 00000000..1e84071f --- /dev/null +++ b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/all.png.mcmeta @@ -0,0 +1 @@ +{"gui":{"scaling":{"type":"nine_slice","width":16,"height":16,"border":{"left":2,"top":2,"right":2,"bottom":2}}}} \ No newline at end of file diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/left.png b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/left.png new file mode 100644 index 00000000..ec6a3762 Binary files /dev/null and b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/left.png differ diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/left.png.mcmeta b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/left.png.mcmeta new file mode 100644 index 00000000..20bc0951 --- /dev/null +++ b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/left.png.mcmeta @@ -0,0 +1 @@ +{"gui":{"scaling":{"type":"nine_slice","width":16,"height":16,"border":{"left":2,"top":2,"right":1,"bottom":2}}}} \ No newline at end of file diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/right.png b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/right.png new file mode 100644 index 00000000..0dc46073 Binary files /dev/null and b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/right.png differ diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/right.png.mcmeta b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/right.png.mcmeta new file mode 100644 index 00000000..71345928 --- /dev/null +++ b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/background/right.png.mcmeta @@ -0,0 +1 @@ +{"gui":{"scaling":{"type":"nine_slice","width":16,"height":16,"border":{"left":1,"top":2,"right":2,"bottom":2}}}} \ No newline at end of file diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/rounded_1.png b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/rounded_1.png new file mode 100644 index 00000000..35e21785 Binary files /dev/null and b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/rounded_1.png differ diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/rounded_1.png.mcmeta b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/rounded_1.png.mcmeta new file mode 100644 index 00000000..03edb6c1 --- /dev/null +++ b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/rounded_1.png.mcmeta @@ -0,0 +1 @@ +{"gui":{"scaling":{"type":"nine_slice","width":10,"height":10,"border":{"left":2,"top":2,"right":2,"bottom":2}}}} \ No newline at end of file diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/toggle/off.png b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/toggle/off.png new file mode 100644 index 00000000..cabbca26 Binary files /dev/null and b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/toggle/off.png differ diff --git a/src/main/resources/assets/islandutils/textures/gui/sprites/widget/toggle/on.png b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/toggle/on.png new file mode 100644 index 00000000..be5971d4 Binary files /dev/null and b/src/main/resources/assets/islandutils/textures/gui/sprites/widget/toggle/on.png differ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index cd7cd3a1..371ea3e0 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,37 +1,37 @@ { - "schemaVersion": 1, - "id": "islandutils", - "version": "${version}", - "name": "IslandUtils", - "description": "A Utility mod for MCC Island", - "authors": ["Aso"], - "contact": { - "sources": "https://github.com/AsoDesu/IslandUtils", - "homepage": "https://modrinth.com/mod/island-utils" - }, - "license": "MIT", - "icon": "assets/island/icon.png", - "environment": "client", - "entrypoints": { - "client": [ "net.asodev.islandutils.IslandUtilsClient" ], - "main": [ "net.asodev.islandutils.IslandUtils" ], - "modmenu": [ "net.asodev.islandutils.options.ModMenuIntegration" ] - }, - "mixins": [ - "islandutils.mixins.json" - ], - "custom": { - "modmenu": { - "links": { - "website": "https://modrinth.com/mod/island-utils" - } - } - }, - "depends": { - "noxesium": ">=2.3", - "fabricloader": ">=0.15", - "fabric": "*", - "minecraft": ">=1.21", - "yet_another_config_lib_v3": "*" - } -} + "schemaVersion": 1, + "id": "islandutils", + "version": "${version}", + "name": "IslandUtils", + "description": "This is an example description! Tell everyone what your mod is about!", + "authors": [ + "AsoDesu_" + ], + "contact": { + "homepage": "https://github.com/AsoDesu/IslandUtils", + "sources": "https://github.com/AsoDesu/IslandUtils" + }, + "license": "CC0-1.0", + "icon": "assets/islandutils/icon.png", + "environment": "client", + "entrypoints": { + "noxesium": [ + { + "value": "dev.asodesu.islandutils.IslandUtils", + "adapter": "kotlin" + } + ] + }, + "accessWidener" : "islandutils.accesswidener", + "mixins": [ + "islandutils.mixins.json" + ], + "depends": { + "fabricloader": ">=0.16.5", + "minecraft": "~1.21.5", + "java": ">=21", + "fabric-api": "*", + "fabric-language-kotlin": "*", + "noxesium": ">=2.7.2" + } +} \ No newline at end of file diff --git a/src/main/resources/islandutils.accesswidener b/src/main/resources/islandutils.accesswidener index d0a4af01..bf8b3940 100644 --- a/src/main/resources/islandutils.accesswidener +++ b/src/main/resources/islandutils.accesswidener @@ -1,6 +1,9 @@ -accessWidener v1 named +accessWidener v2 named -#accessible method net/minecraft/server/packs/FilePackResources (Ljava/lang/String;Lnet/minecraft/server/packs/FilePackResources$SharedZipFileAccess;ZLjava/lang/String;)V -#accessible method net/minecraft/server/packs/FilePackResources$SharedZipFileAccess (Ljava/io/File;)V -#accessible method net/minecraft/util/HttpUtil downloadAndHash (Lcom/google/common/hash/HashFunction;ILnet/minecraft/util/HttpUtil$DownloadProgressListener;Ljava/io/InputStream;Ljava/nio/file/Path;)Lcom/google/common/hash/HashCode; -#accessible method net/minecraft/client/resources/server/DownloadedPackSource createDownloadNotifier (I)Lnet/minecraft/util/HttpUtil$DownloadProgressListener; \ No newline at end of file +accessible field net/minecraft/client/sounds/SoundManager registry Ljava/util/Map; +accessible field net/minecraft/client/sounds/SoundManager soundCache Ljava/util/Map; +accessible field net/minecraft/client/player/LocalPlayer crouching Z +accessible class net/minecraft/client/sounds/SoundManager$Preparations +accessible class net/minecraft/client/gui/font/FontManager$UnresolvedBuilderBundle +accessible field net/minecraft/world/item/component/CustomData tag Lnet/minecraft/nbt/CompoundTag; +accessible method net/minecraft/client/gui/screens/inventory/InventoryScreen extractRenderState (Lnet/minecraft/world/entity/LivingEntity;)Lnet/minecraft/client/renderer/entity/state/EntityRenderState; \ No newline at end of file diff --git a/src/main/resources/islandutils.mixins.json b/src/main/resources/islandutils.mixins.json index 9ca0dd57..02c88564 100644 --- a/src/main/resources/islandutils.mixins.json +++ b/src/main/resources/islandutils.mixins.json @@ -1,42 +1,32 @@ { - "required": true, - "minVersion": "0.8", - "package": "net.asodev.islandutils.mixins", - "compatibilityLevel": "JAVA_17", - "refmap": "islandutils.refmap.json", - "mixins": [ - "ItemIDMixin", - "accessors.WalkAnimStateAccessor", - "cosmetics.ChestScreenMixin", - "cosmetics.FontLoaderMixin", - "cosmetics.PreviewTutorialMixin", - "discord.ConnectionMixin", - "discord.ScoreDisplayMixin", - "resources.PackRepositoryMixin", - "ui.SlotMixin" - ], - "client": [ - "BossUIMixin", - "accessors.ContainerScreenAccessor", - "accessors.SoundEngineAccessor", - "accessors.SoundManagerAccessor", - "cosmetics.UIMixin", - "crafting.CraftingPacketMixin", - "crafting.RemnantHighlightMixin", - "crafting.ScavengingScreenMixin", - "discord.JoinMCCIMixin", - "network.PacketListenerMixin", - "notif.ServerSelectionMixin", - "plobby.PlobbyChestMixin", - "resources.MultiplayerJoinMixin", - "splits.HologramMixin", - "ui.ChatScreenMixin", - "ui.CommandSuggestionsAccessor", - "ui.FishingUpgradeHighlightMixin", - "ui.OptionsMixin", - "ui.PauseScreenMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} + "required": true, + "package": "dev.asodesu.islandutils.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "BossbarInterceptorMixin", + "HideBlankSlotsMixin", + "ItemTooltipMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "client": [ + "AddChatChannelButtonsMixin", + "ConfirmDisconnectButtonMixin", + "FontLoaderMixin", + "OptionsButtonMixin", + "PlayerCloakRenderLayerMixin", + "SidebarInterceptorMixin", + "SoundInterceptorMixin", + "TitleInterceptorMixin", + "chest.ChestMenuInterceptorMixin", + "chest.ChestScreenInterceptorMixin", + "features.CommandSuggestionHandlerMixin", + "server.InstanceJoinMixin", + "server.ServerJoinMixin", + "server.ServerDisconnectMixin", + "serverlist.JoinMultiplayerTickMixin", + "serverlist.ServerListEntryRenderMixin", + "sound.ApplyInjectedSoundsMixin" + ] +} \ No newline at end of file diff --git a/src/main/resources/native/lib/aarch64/discord_game_sdk.bundle b/src/main/resources/native/lib/aarch64/discord_game_sdk.bundle deleted file mode 100644 index 8f086062..00000000 Binary files a/src/main/resources/native/lib/aarch64/discord_game_sdk.bundle and /dev/null differ diff --git a/src/main/resources/native/lib/aarch64/discord_game_sdk.dylib b/src/main/resources/native/lib/aarch64/discord_game_sdk.dylib deleted file mode 100644 index 8f086062..00000000 Binary files a/src/main/resources/native/lib/aarch64/discord_game_sdk.dylib and /dev/null differ diff --git a/src/main/resources/native/lib/x86/discord_game_sdk.dll b/src/main/resources/native/lib/x86/discord_game_sdk.dll deleted file mode 100644 index 45b9bb17..00000000 Binary files a/src/main/resources/native/lib/x86/discord_game_sdk.dll and /dev/null differ diff --git a/src/main/resources/native/lib/x86/discord_game_sdk.dll.lib b/src/main/resources/native/lib/x86/discord_game_sdk.dll.lib deleted file mode 100644 index 02c417e6..00000000 Binary files a/src/main/resources/native/lib/x86/discord_game_sdk.dll.lib and /dev/null differ diff --git a/src/main/resources/native/lib/x86_64/discord_game_sdk.bundle b/src/main/resources/native/lib/x86_64/discord_game_sdk.bundle deleted file mode 100644 index 3402426b..00000000 Binary files a/src/main/resources/native/lib/x86_64/discord_game_sdk.bundle and /dev/null differ diff --git a/src/main/resources/native/lib/x86_64/discord_game_sdk.dll b/src/main/resources/native/lib/x86_64/discord_game_sdk.dll deleted file mode 100644 index be946ea7..00000000 Binary files a/src/main/resources/native/lib/x86_64/discord_game_sdk.dll and /dev/null differ diff --git a/src/main/resources/native/lib/x86_64/discord_game_sdk.dll.lib b/src/main/resources/native/lib/x86_64/discord_game_sdk.dll.lib deleted file mode 100644 index 562b01cd..00000000 Binary files a/src/main/resources/native/lib/x86_64/discord_game_sdk.dll.lib and /dev/null differ diff --git a/src/main/resources/native/lib/x86_64/discord_game_sdk.dylib b/src/main/resources/native/lib/x86_64/discord_game_sdk.dylib deleted file mode 100644 index 3402426b..00000000 Binary files a/src/main/resources/native/lib/x86_64/discord_game_sdk.dylib and /dev/null differ diff --git a/src/main/resources/native/lib/x86_64/discord_game_sdk.so b/src/main/resources/native/lib/x86_64/discord_game_sdk.so deleted file mode 100644 index 9dacf946..00000000 Binary files a/src/main/resources/native/lib/x86_64/discord_game_sdk.so and /dev/null differ diff --git a/src/main/resources/native/linux/amd64/libdiscord_game_sdk_jni.so b/src/main/resources/native/linux/amd64/libdiscord_game_sdk_jni.so deleted file mode 100644 index 8e36260c..00000000 Binary files a/src/main/resources/native/linux/amd64/libdiscord_game_sdk_jni.so and /dev/null differ diff --git a/src/main/resources/native/macos/amd64/libdiscord_game_sdk_jni.dylib b/src/main/resources/native/macos/amd64/libdiscord_game_sdk_jni.dylib deleted file mode 100644 index 82a22f4d..00000000 Binary files a/src/main/resources/native/macos/amd64/libdiscord_game_sdk_jni.dylib and /dev/null differ diff --git a/src/main/resources/native/windows/amd64/discord_game_sdk_jni.dll b/src/main/resources/native/windows/amd64/discord_game_sdk_jni.dll deleted file mode 100644 index 9125a736..00000000 Binary files a/src/main/resources/native/windows/amd64/discord_game_sdk_jni.dll and /dev/null differ diff --git a/src/main/resources/native/windows/x86/discord_game_sdk_jni.dll b/src/main/resources/native/windows/x86/discord_game_sdk_jni.dll deleted file mode 100644 index dfb803a6..00000000 Binary files a/src/main/resources/native/windows/x86/discord_game_sdk_jni.dll and /dev/null differ