├── .editorconfig ├── .github └── workflows │ ├── build.yml │ ├── dco.yml │ ├── pull_request.yml │ ├── release.yml │ ├── release_dev.yml │ ├── release_pre.yml │ └── release_rc.yml ├── .gitignore ├── .idea └── icon.png ├── CHANGELOG.md ├── Jenkinsfile ├── LICENSE ├── README.md ├── api ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── api │ │ ├── config │ │ │ └── v1 │ │ │ │ ├── ZumeConfig.java │ │ │ │ └── ZumeConfigAPI.java │ │ └── platform │ │ │ └── v1 │ │ │ ├── CameraPerspective.java │ │ │ ├── IZumeImplementation.java │ │ │ └── ZumeAPI.java │ │ └── impl │ │ ├── CameraPerspective.java │ │ ├── EasedDouble.java │ │ ├── HostPlatform.java │ │ ├── IZumeImplementation.java │ │ ├── Zume.java │ │ ├── config │ │ ├── FileWatcher.java │ │ ├── IFileWatcher.java │ │ ├── NullFileWatcher.java │ │ ├── ZumeConfigImpl.java │ │ └── package-info.java │ │ └── package-info.java │ └── resources │ ├── assets │ └── zume │ │ └── lang │ │ ├── en_US.lang │ │ └── en_us.json │ ├── icon.png │ └── icon.xcf ├── archaic ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── archaic │ │ ├── ArchaicConfigProvider.java │ │ ├── ArchaicZume.java │ │ ├── ArchaicZumeConfigGUI.java │ │ └── ZumeKeyBind.java │ │ └── mixin │ │ └── archaic │ │ ├── EntityRendererAccessor.java │ │ └── EntityRendererMixin.java │ └── resources │ ├── mcmod.info │ └── zume-archaic.mixins.json ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── Zume.kt │ └── dev │ └── nolij │ └── zumegradle │ ├── DeflateAlgorithm.kt │ ├── JsonShrinkingType.kt │ ├── MixinConfigMergingTransformer.kt │ ├── entryprocessing │ └── EntryProcessors.kt │ ├── smoketest │ ├── MojangMetaApi.kt │ └── SmokeTest.kt │ ├── task │ ├── AdvzipTask.kt │ ├── CopyJarTask.kt │ ├── JarEntryModificationTask.kt │ ├── JvmdgStubCheckTask.kt │ ├── ProcessJarTask.kt │ ├── ProguardTask.kt │ └── SmokeTestTask.kt │ └── util │ ├── AsmHelper.kt │ └── MappingsHelper.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── icon_padded.png ├── icon_padded_large.png ├── integration ├── build.gradle.kts └── embeddium │ ├── build.gradle.kts │ └── src │ └── main │ └── java │ └── dev │ └── nolij │ └── zume │ └── integration │ ├── embeddium │ └── ZumeOptionsStorage.java │ └── implementation │ └── embeddium │ └── ZumeEmbeddiumConfigScreen.java ├── legacy ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── legacy │ │ ├── LegacyZume.java │ │ └── ZumeKeyBind.java │ │ └── mixin │ │ └── legacy │ │ ├── GameRendererAccessor.java │ │ ├── GameRendererMixin.java │ │ ├── KeyBindingMixin.java │ │ └── MinecraftClientMixin.java │ └── resources │ ├── fabric.mod.json │ └── zume-legacy.mixins.json ├── lexforge ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── lexforge │ │ ├── LexZume.java │ │ ├── LexZumeConfigScreen.java │ │ └── ZumeKeyBind.java │ │ └── mixin │ │ └── lexforge │ │ ├── CameraMixin.java │ │ └── MouseHandlerMixin.java │ └── resources │ ├── META-INF │ └── mods.toml │ ├── pack.mcmeta │ └── zume-lexforge.mixins.json ├── lexforge16 ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── lexforge16 │ │ ├── LexZume16.java │ │ ├── LexZume16ConfigScreen.java │ │ └── ZumeKeyBind.java │ │ └── mixin │ │ └── lexforge16 │ │ ├── CameraMixin.java │ │ └── MouseHandlerMixin.java │ └── resources │ ├── META-INF │ └── mods.toml │ ├── pack.mcmeta │ └── zume-lexforge16.mixins.json ├── lexforge18 ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── lexforge18 │ │ ├── LexZume18.java │ │ ├── LexZume18ConfigScreen.java │ │ └── ZumeKeyBind.java │ │ └── mixin │ │ └── lexforge18 │ │ ├── CameraMixin.java │ │ └── MouseHandlerMixin.java │ └── resources │ ├── META-INF │ └── mods.toml │ ├── pack.mcmeta │ └── zume-lexforge18.mixins.json ├── modern ├── build.gradle.kts └── src │ └── main │ ├── java │ ├── dev │ │ └── nolij │ │ │ └── zume │ │ │ ├── mixin │ │ │ └── modern │ │ │ │ ├── CameraMixin.java │ │ │ │ ├── GameRendererMixin.java │ │ │ │ └── MouseHandlerMixin.java │ │ │ └── modern │ │ │ ├── ModernZume.java │ │ │ ├── ZumeKeyBind.java │ │ │ └── integration │ │ │ └── modmenu │ │ │ ├── ModernZumeConfigScreen.java │ │ │ └── ZumeModMenuIntegration.java │ └── io │ │ └── github │ │ └── prospector │ │ └── modmenu │ │ └── api │ │ └── ModMenuApi.java │ └── resources │ ├── fabric.mod.json │ └── zume-modern.mixins.json ├── neoforge ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ └── neoforge │ │ ├── NeoZume.java │ │ ├── NeoZumeConfigScreen.java │ │ ├── NeoZumeConfigScreenFactory.java │ │ ├── ZumeKeyBind.java │ │ └── integration │ │ └── embeddium │ │ ├── ZumeEmbeddiumConfigScreen.java │ │ └── ZumeOptionsStorage.java │ └── resources │ └── META-INF │ ├── mods.toml │ └── neoforge.mods.toml ├── primitive ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── mixin │ │ └── primitive │ │ │ ├── GameRendererAccessor.java │ │ │ ├── GameRendererMixin.java │ │ │ ├── MinecraftAccessor.java │ │ │ └── MinecraftMixin.java │ │ └── primitive │ │ ├── PrimitiveZume.java │ │ └── ZumeKeyBind.java │ └── resources │ ├── fabric.mod.json │ └── zume-primitive.mixins.json ├── proguard.pro ├── settings.gradle.kts ├── src └── main │ ├── java │ └── dev │ │ └── nolij │ │ └── zume │ │ ├── FabricZumeBootstrapper.java │ │ ├── ForgeZumeBootstrapper.java │ │ └── ZumeMixinPlugin.java │ └── resources │ ├── META-INF │ └── mods.toml │ ├── fabric.mod.json │ ├── mcmod.info │ └── pack.mcmeta ├── stubs ├── build.gradle.kts └── src │ └── main │ └── java │ ├── cpw │ └── mods │ │ └── fml │ │ └── common │ │ └── eventhandler │ │ └── EventPriority.java │ ├── dev │ └── nolij │ │ └── zumegradle │ │ └── proguard │ │ └── ProGuardKeep.java │ └── net │ └── minecraftforge │ ├── eventbus │ └── api │ │ └── EventPriority.java │ └── fml │ └── common │ ├── Mod.java │ └── eventhandler │ └── EventPriority.java └── vintage ├── build.gradle.kts └── src └── main ├── java └── dev │ └── nolij │ └── zume │ ├── mixin │ └── vintage │ │ └── EntityRendererMixin.java │ └── vintage │ ├── VintageConfigProvider.java │ ├── VintageZume.java │ ├── VintageZumeConfigGUI.java │ └── ZumeKeyBind.java └── resources ├── mcmod.info └── zume-vintage.mixins.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [ push ] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | 10 | - name: Install Packages 11 | run: sudo apt-get install -y advancecomp 12 | 13 | # cache local gradle files, global ones will be taken care of by the setup-gradle action 14 | - uses: actions/cache@v4 15 | with: 16 | path: | 17 | **/.gradle/ 18 | **/build/ 19 | key: ${{ runner.os }}-gradlelocal-${{ github.ref }} 20 | 21 | - uses: actions/setup-java@v4 22 | with: 23 | distribution: temurin 24 | java-version: 21 25 | 26 | - name: Setup Gradle 27 | uses: gradle/actions/setup-gradle@v4 28 | with: 29 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 30 | 31 | - name: :build 32 | run: ./gradlew build --stacktrace --no-daemon -PwithAuditAndExit=true 33 | 34 | - name: Upload artifacts 35 | if: always() 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: zume 39 | path: | 40 | **/build/libs/zume*.jar 41 | build/libs/*mappings.txt 42 | 43 | - name: :smokeTest 44 | id: smokeTest 45 | uses: coactions/setup-xvfb@v1 46 | with: 47 | run: ./gradlew :smokeTest --no-daemon -PwithAuditAndExit=true 48 | 49 | - name: Upload test results 50 | if: failure() 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: smokeTest 54 | path: | 55 | build/smoke_test/**/logs/** 56 | build/smoke_test/**/setup.log 57 | -------------------------------------------------------------------------------- /.github/workflows/dco.yml: -------------------------------------------------------------------------------- 1 | name: "DCO Assistant" 2 | on: 3 | issue_comment: 4 | types: [created] 5 | pull_request_target: 6 | types: [opened, closed, synchronize] 7 | 8 | permissions: 9 | actions: write 10 | contents: write 11 | pull-requests: write 12 | statuses: write 13 | 14 | jobs: 15 | DCO: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: "DCO Assistant" 19 | if: ( 20 | github.event.comment.body == 'recheck' || 21 | github.event.comment.body == 'I have read and hereby affirm the entire contents of the Developer Certificate of Origin.' 22 | ) || github.event_name == 'pull_request_target' 23 | uses: cla-assistant/github-action@v2.3.0 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | path-to-signatures: "signatures.json" 28 | path-to-document: "https://github.com/Nolij/Zume/blob/dco/DCO.txt" 29 | branch: "dco" 30 | create-file-commit-message: "Create DCO signature list" 31 | signed-commit-message: "Add $contributorName to the DCO signature list" 32 | custom-notsigned-prcomment: 33 | "Before we can merge your submission, we require that you read and affirm the contents of the 34 | [Developer Certificate of Origin](https://github.com/Nolij/Zume/blob/dco/DCO.txt) by adding a comment containing the below text. 35 | Otherwise, please close this PR." 36 | custom-pr-sign-comment: "I have read and hereby affirm the entire contents of the Developer Certificate of Origin." 37 | custom-allsigned-prcomment: "All contributors have read and affirmed the entire contents of the Developer Certificate of Origin." 38 | use-dco-flag: true 39 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: pull_request 2 | on: [ pull_request ] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Install Packages 10 | run: sudo apt-get install -y advancecomp 11 | - uses: actions/setup-java@v4 12 | with: 13 | distribution: temurin 14 | java-version: 21 15 | - name: Setup Gradle 16 | uses: gradle/actions/setup-gradle@v4 17 | with: 18 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY_PR }} 19 | cache-read-only: true 20 | - name: :build 21 | run: ./gradlew build --no-daemon -PwithAuditAndExit=true 22 | - name: :smokeTest 23 | uses: coactions/setup-xvfb@v1 24 | with: 25 | run: ./gradlew :smokeTest --no-daemon -PwithAuditAndExit=true 26 | - name: Upload artifacts 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: zume 30 | path: | 31 | **/zume-*.jar 32 | **/*mappings.txt 33 | build/smoke_test/**/*.log 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: :build 24 | run: ./gradlew :build -PwithAuditAndExit=true 25 | - name: :smokeTest 26 | uses: coactions/setup-xvfb@v1 27 | with: 28 | run: ./gradlew :smokeTest -PwithAuditAndExit=true 29 | - name: :publishMods 30 | run: ./gradlew publishMods -Prelease_channel=RELEASE --no-daemon 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} 34 | CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} 35 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} -------------------------------------------------------------------------------- /.github/workflows/release_dev.yml: -------------------------------------------------------------------------------- 1 | name: release_dev 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: :publishMods 24 | run: ./gradlew publishMods -Prelease_channel=DEV_BUILD --stacktrace --no-daemon 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_DEV_WEBHOOK }} -------------------------------------------------------------------------------- /.github/workflows/release_pre.yml: -------------------------------------------------------------------------------- 1 | name: release_pre 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: :publishMods 24 | run: ./gradlew publishMods -Prelease_channel=PRE_RELEASE --no-daemon 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_DEV_WEBHOOK }} -------------------------------------------------------------------------------- /.github/workflows/release_rc.yml: -------------------------------------------------------------------------------- 1 | name: release_rc 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: :publishMods 24 | run: ./gradlew publishMods -Prelease_channel=RELEASE_CANDIDATE --no-daemon 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_DEV_WEBHOOK }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | zume.json5 4 | 5 | *.iml 6 | *.ipr 7 | *.iws 8 | 9 | # IntelliJ 10 | out/ 11 | # mpeltonen/sbt-idea plugin 12 | .idea_modules/ 13 | 14 | # JIRA plugin 15 | atlassian-ide-plugin.xml 16 | 17 | # Compiled class file 18 | *.class 19 | 20 | # Log file 21 | *.log 22 | 23 | # BlueJ files 24 | *.ctxt 25 | 26 | # Package Files # 27 | *.jar 28 | *.war 29 | *.nar 30 | *.ear 31 | *.zip 32 | *.tar.gz 33 | *.rar 34 | 35 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 36 | hs_err_pid* 37 | 38 | *~ 39 | 40 | # temporary files which can be created if a process still has a handle open of a deleted file 41 | .fuse_hidden* 42 | 43 | # KDE directory preferences 44 | .directory 45 | 46 | # Linux trash folder which might appear on any partition or disk 47 | .Trash-* 48 | 49 | # .nfs files are created when an open file is removed but is still being accessed 50 | .nfs* 51 | 52 | # General 53 | .DS_Store 54 | .AppleDouble 55 | .LSOverride 56 | 57 | # Icon must end with two \r 58 | Icon 59 | 60 | # Thumbnails 61 | ._* 62 | 63 | # Files that might appear in the root of a volume 64 | .DocumentRevisions-V100 65 | .fseventsd 66 | .Spotlight-V100 67 | .TemporaryItems 68 | .Trashes 69 | .VolumeIcon.icns 70 | .com.apple.timemachine.donotpresent 71 | 72 | # Directories potentially created on remote AFP share 73 | .AppleDB 74 | .AppleDesktop 75 | Network Trash Folder 76 | Temporary Items 77 | .apdisk 78 | 79 | # Windows thumbnail cache files 80 | Thumbs.db 81 | Thumbs.db:encryptable 82 | ehthumbs.db 83 | ehthumbs_vista.db 84 | 85 | # Dump file 86 | *.stackdump 87 | 88 | # Folder config file 89 | [Dd]esktop.ini 90 | 91 | # Recycle Bin used on file shares 92 | $RECYCLE.BIN/ 93 | 94 | # Windows Installer files 95 | *.cab 96 | *.msi 97 | *.msix 98 | *.msm 99 | *.msp 100 | 101 | # Windows shortcuts 102 | *.lnk 103 | 104 | .gradle 105 | build/ 106 | 107 | # Ignore Gradle GUI config 108 | gradle-app.setting 109 | 110 | # Cache of project 111 | .gradletasknamecache 112 | 113 | **/build/ 114 | 115 | # Common working directory 116 | run/ 117 | 118 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 119 | !gradle-wrapper.jar 120 | -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/Zume/1b8db932dd5fa62dd89a096638bd0f78baa8e6f0/.idea/icon.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - many internal improvements (thanks @rhysdh540) 2 | - fixed Babric 3 | - fixed Vintage Forge issue when Zume is installed on the server and not the client 4 | - added automated smoke testing (this will significantly reduce the development burden of supporting so many platforms) 5 | - added LibNolij (this should shrink the JAR a bit) 6 | - further improvements to overall system stability and other minor adjustments have been made to enhance the user experience 7 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | pipeline { 4 | agent any 5 | 6 | tools { 7 | jdk "jdk-21" 8 | } 9 | 10 | stages { 11 | stage("Setup") { 12 | steps { 13 | sh "chmod +x gradlew" 14 | } 15 | } 16 | 17 | stage(":publish") { 18 | steps { 19 | sh "./gradlew publish -Pexternal_publish=true" 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT LICENSE NOTICE 2 | 3 | By using this project in any form, you hereby give your "express assent" for the terms of the license of this 4 | project (see [License](#license)), and acknowledge that I (the author of this project) have fulfilled my obligation 5 | under the license to "make a reasonable effort under the circumstances to obtain the express assent of recipients to 6 | the terms of this License". 7 | 8 | # Zume 9 | 10 | An ~~over-engineered~~ simple zoom mod. 11 | 12 | This mod adds a keybind which zooms in your FOV while it's held down, allowing you to see further away, and keybinds 13 | for increasing and decreasing the zoom level. 14 | 15 | # FAQ 16 | 17 | #### Q: Where is the config? 18 | 19 | A: You'll find the config at `.minecraft/global/zume.json5` (note that this is the default `.minecraft` folder, not 20 | the instance `.minecraft`). You can modify the file while the game is running, and the config will be automatically 21 | reloaded. On platforms which have mod menus (such as Forge, and Fabric with [Mod Menu](https://modrinth.com/mod/modmenu) 22 | installed), the config button for Zume will open this file in your system text editor. 23 | 24 | #### Q: discord where 25 | A: https://discord.gg/6ZjX4mvCMR 26 | 27 | #### Q: What version is this for? 28 | 29 | A: Zume supports the following platforms: 30 | 31 | - Fabric: Any version supported by Fabric Keybinding API v1 (currently 14.4+) 32 | - NeoForge: 20.4+ (requires NeoForge 20.4.195+) 33 | - LexForge: 14.4 - 20.5 (requires MixinBootstrap before 16.1) (there are currently no plans to support 20.6+) 34 | - Legacy Fabric: Any version supported by Legacy Fabric Keybinding API v1 (currently 6.4 - 12.2) 35 | - Babric (Fabric for Minecraft Beta): Any version supported by Station API (currently b7.3) 36 | - Vintage Forge: 8.9 - 12.2 (requires MixinBooter or UniMixins) 37 | - Archaic Forge: 7.10 (requires UniMixins) 38 | 39 | #### Q: Can you add support for \? 40 | 41 | A: Every platform I intend to add support for myself is already supported. PRs are welcome for other platforms **if 42 | the following conditions are met**: 43 | 44 | - Must not break single-jar compatibility with any already supported platform (obviously). 45 | - Must not be for a platform that has a 1st-party compatibility layer for an already supported platform - explicit 46 | Quilt support will not be accepted so long as Quilt maintains a Fabric compatibility layer; it'd be a waste of CI 47 | time. [Sinytra Connector](https://github.com/Sinytra/Connector) is a 3rd-party compatibility layer, so explicit 48 | Forge support will be provided. 49 | - Must not manually maintain overridden game options; implementations that look like 50 | [Mooz's](https://github.com/embeddedt/Mooz/blob/92570f7449a7e71c1c0b988788027b10c00f1346/src/main/java/org/embeddedt/mooz/ClientProxy.java#L35-L56) 51 | will not be accepted - no offense [embeddedt](https://github.com/embeddedt). Direct ASM is fine as long as functionality is similar enough. 52 | - Must make a reasonable effort to be maximize compatibility with existing mods on target platforms - see use of 53 | Neo/LexForge API over mixins in Neo/LexForge implementations, and use of `@WrapWithCondition` and 54 | `@ModifyExpressionValue` and such from MixinExtras over `@Redirect` in most implementations. 55 | - Must follow existing format - add a Unimined subproject for each newly supported platform. 56 | - Must not have exclusive features without significant justification - if you're adding a feature, add it to every 57 | version. 58 | 59 | #### Q: What kind of weird license is this? 60 | 61 | A: OSL-3.0 is the closest equivalent to a LAGPL I could find. AGPL and GPL are incompatible with Minecraft, and LGPL 62 | doesn't protect network use. OSL-3.0 protects network use and is compatible with Minecraft. 63 | 64 | #### Q: Why though? It's so strict!!!! 65 | 66 | A: This is, and will remain, free, copyleft software. Any requests to change the license other than to make it even 67 | stronger will be denied immediately (unfortunately GPL and AGPL aren't compatible with Minecraft due to linking 68 | restrictions, as much as I'd like to use them). Even in situations where I use parts of other projects with more 69 | "permissive" licenses, I will treat them as copyleft, free software. 70 | 71 | ## License 72 | 73 | This project is licensed under OSL-3.0. For more information, see [LICENSE](LICENSE). 74 | 75 | # ![YourKit](https://www.yourkit.com/images/yklogo.png) 76 | 77 | Zume uses [YourKit](https://www.yourkit.com) for ensuring code efficiency. 78 | 79 | YourKit supports open source projects with innovative and intelligent tools 80 | for monitoring and profiling Java and .NET applications. 81 | YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), 82 | [YourKit .NET Profiler](https://www.yourkit.com/dotnet-profiler/), 83 | and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). -------------------------------------------------------------------------------- /api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.github.gmazzo.buildconfig") 3 | } 4 | 5 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 6 | 7 | buildConfig { 8 | className("ZumeConstants") 9 | packageName("dev.nolij.zume.impl") 10 | 11 | useJavaOutput() 12 | 13 | // the below errors shown by IntelliJ can be safely ignored; Jabel works around this 14 | buildConfigField("MOD_ID", "mod_id"()) 15 | buildConfigField("MOD_VERSION", Zume.version) 16 | buildConfigField("MOD_NAME", "mod_name"()) 17 | buildConfigField("ARCHAIC_VERSION_RANGE", "archaic_minecraft_range"()) 18 | buildConfigField("VINTAGE_VERSION_RANGE", "vintage_minecraft_range"()) 19 | buildConfigField("AUDIT_AND_EXIT_ENABLED", Zume.auditAndExitEnabled) 20 | } 21 | 22 | dependencies { 23 | compileOnly("org.apache.logging.log4j:log4j-core:${"log4j_version"()}") 24 | } 25 | 26 | tasks.clean { 27 | finalizedBy(tasks.generateBuildConfig) 28 | } 29 | 30 | tasks.processResources { 31 | from("src/main/resources/assets/zume/lang/") { 32 | include("*.lang") 33 | rename { name -> name.lowercase() } 34 | into("assets/zume/lang/") 35 | } 36 | 37 | from("src/main/resources/assets/zume/lang/") { 38 | include("*.lang") 39 | into("assets/zume/stationapi/lang/") 40 | } 41 | } -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/api/config/v1/ZumeConfig.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.api.config.v1; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | 5 | @ApiStatus.NonExtendable 6 | public final class ZumeConfig { 7 | 8 | public boolean isCinematicZoomEnabled; 9 | public double mouseSensitivityFloor; 10 | public short zoomSpeed; 11 | public boolean isZoomScrollingEnabled; 12 | public short zoomSmoothnessMilliseconds; 13 | public double animationEasingExponent; 14 | public double zoomEasingExponent; 15 | public double defaultZoom; 16 | public boolean isFirstPersonToggleModeEnabled; 17 | public boolean isThirdPersonToggleModeEnabled; 18 | public double minimumFOV; 19 | public double maximumThirdPersonZoomBlocks; 20 | public double minimumThirdPersonZoomBlocks; 21 | public boolean isDisabled; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/api/config/v1/ZumeConfigAPI.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.api.config.v1; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import dev.nolij.zume.impl.config.ZumeConfigImpl; 5 | import org.jetbrains.annotations.ApiStatus; 6 | import org.jetbrains.annotations.Contract; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @ApiStatus.NonExtendable 10 | public final class ZumeConfigAPI { 11 | 12 | @Contract(pure = true) 13 | public static boolean isCinematicZoomEnabled() { 14 | return Zume.config.enableCinematicZoom; 15 | } 16 | 17 | @Contract(pure = true) 18 | public static double getMouseSensitivityFloor() { 19 | return Zume.config.mouseSensitivityFloor; 20 | } 21 | 22 | @Contract(pure = true) 23 | public static short getZoomSpeed() { 24 | return Zume.config.zoomSpeed; 25 | } 26 | 27 | @Contract(pure = true) 28 | public static boolean isZoomScrollingEnabled() { 29 | return Zume.config.enableZoomScrolling; 30 | } 31 | 32 | @Contract(pure = true) 33 | public static short getZoomSmoothnessMilliseconds() { 34 | return Zume.config.zoomSmoothnessMs; 35 | } 36 | 37 | @Contract(pure = true) 38 | public static double getAnimationEasingExponent() { 39 | return Zume.config.animationEasingExponent; 40 | } 41 | 42 | @Contract(pure = true) 43 | public static double getZoomEasingExponent() { 44 | return Zume.config.zoomEasingExponent; 45 | } 46 | 47 | @Contract(pure = true) 48 | public static double getDefaultZoom() { 49 | return Zume.config.defaultZoom; 50 | } 51 | 52 | @Contract(pure = true) 53 | public static boolean isFirstPersonToggleModeEnabled() { 54 | return Zume.config.toggleMode; 55 | } 56 | 57 | @Contract(pure = true) 58 | public static boolean isThirdPersonToggleModeEnabled() { 59 | return Zume.config.thirdPersonToggleMode; 60 | } 61 | 62 | @Contract(pure = true) 63 | public static double getMinimumFOV() { 64 | return Zume.config.minFOV; 65 | } 66 | 67 | @Contract(pure = true) 68 | public static double getMaximumThirdPersonZoomBlocks() { 69 | return Zume.config.maxThirdPersonZoomDistance; 70 | } 71 | 72 | @Contract(pure = true) 73 | public static double getMinimumThirdPersonZoomBlocks() { 74 | return Zume.config.minThirdPersonZoomDistance; 75 | } 76 | 77 | @Contract(pure = true) 78 | public static boolean isDisabled() { 79 | return Zume.config.disable; 80 | } 81 | 82 | public static @NotNull ZumeConfig getSnapshot() { 83 | final ZumeConfig snapshot = new ZumeConfig(); 84 | 85 | snapshot.isCinematicZoomEnabled = isCinematicZoomEnabled(); 86 | snapshot.mouseSensitivityFloor = getMouseSensitivityFloor(); 87 | snapshot.zoomSpeed = getZoomSpeed(); 88 | snapshot.isZoomScrollingEnabled = isZoomScrollingEnabled(); 89 | snapshot.zoomSmoothnessMilliseconds = getZoomSmoothnessMilliseconds(); 90 | snapshot.animationEasingExponent = getAnimationEasingExponent(); 91 | snapshot.zoomEasingExponent = getZoomEasingExponent(); 92 | snapshot.defaultZoom = getDefaultZoom(); 93 | snapshot.isFirstPersonToggleModeEnabled = isFirstPersonToggleModeEnabled(); 94 | snapshot.isThirdPersonToggleModeEnabled = isThirdPersonToggleModeEnabled(); 95 | snapshot.minimumFOV = getMinimumFOV(); 96 | snapshot.maximumThirdPersonZoomBlocks = getMaximumThirdPersonZoomBlocks(); 97 | snapshot.minimumThirdPersonZoomBlocks = getMinimumThirdPersonZoomBlocks(); 98 | snapshot.isDisabled = isDisabled(); 99 | 100 | return snapshot; 101 | } 102 | 103 | public static void replaceConfig(@NotNull ZumeConfig replacement) throws InterruptedException { 104 | final ZumeConfigImpl config = new ZumeConfigImpl(); 105 | 106 | config.enableCinematicZoom = replacement.isCinematicZoomEnabled; 107 | config.mouseSensitivityFloor = replacement.mouseSensitivityFloor; 108 | config.zoomSpeed = replacement.zoomSpeed; 109 | config.enableZoomScrolling = replacement.isZoomScrollingEnabled; 110 | config.zoomSmoothnessMs = replacement.zoomSmoothnessMilliseconds; 111 | config.animationEasingExponent = replacement.animationEasingExponent; 112 | config.zoomEasingExponent = replacement.zoomEasingExponent; 113 | config.defaultZoom = replacement.defaultZoom; 114 | config.toggleMode = replacement.isFirstPersonToggleModeEnabled; 115 | config.thirdPersonToggleMode = replacement.isThirdPersonToggleModeEnabled; 116 | config.minFOV = replacement.minimumFOV; 117 | config.maxThirdPersonZoomDistance = replacement.maximumThirdPersonZoomBlocks; 118 | config.minThirdPersonZoomDistance = replacement.minimumThirdPersonZoomBlocks; 119 | config.disable = replacement.isDisabled; 120 | 121 | ZumeConfigImpl.replace(config); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/api/platform/v1/CameraPerspective.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.api.platform.v1; 2 | 3 | public enum CameraPerspective { 4 | 5 | FIRST_PERSON, 6 | THIRD_PERSON, 7 | THIRD_PERSON_FLIPPED, 8 | 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/api/platform/v1/IZumeImplementation.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.api.platform.v1; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.jetbrains.annotations.Contract; 5 | import org.jetbrains.annotations.NonBlocking; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | @ApiStatus.OverrideOnly 9 | public interface IZumeImplementation { 10 | 11 | @Contract(pure = true) boolean isZoomPressed(); 12 | @Contract(pure = true) boolean isZoomInPressed(); 13 | @Contract(pure = true) boolean isZoomOutPressed(); 14 | 15 | @Contract(pure = true) @NotNull CameraPerspective getCameraPerspective(); 16 | 17 | @NonBlocking default void onZoomActivate() {} 18 | 19 | } 20 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/api/platform/v1/ZumeAPI.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.api.platform.v1; 2 | 3 | import dev.nolij.zume.impl.CameraPerspective; 4 | import dev.nolij.zume.impl.Zume; 5 | import org.apache.logging.log4j.Logger; 6 | import org.jetbrains.annotations.ApiStatus; 7 | import org.jetbrains.annotations.Contract; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.nio.file.Path; 11 | 12 | @ApiStatus.NonExtendable 13 | public final class ZumeAPI { 14 | 15 | @Contract(pure = true) 16 | public static Logger getLogger() { 17 | return Zume.LOGGER; 18 | } 19 | 20 | /** 21 | * Attempts to open Zume's config file in the system's text editor. 22 | */ 23 | public static void openConfigFile() { 24 | Zume.openConfigFile(); 25 | } 26 | 27 | /** 28 | * Invoke this in the initializer of your Zume implementation. 29 | * 30 | * @param implementation The {@linkplain IZumeImplementation} Zume should use. 31 | * @param instanceConfigPath The {@linkplain Path} Zume should use for storing the instance-specific config. 32 | */ 33 | public static void registerImplementation(@NotNull final IZumeImplementation implementation, 34 | @NotNull final Path instanceConfigPath) { 35 | Zume.registerImplementation(new dev.nolij.zume.impl.IZumeImplementation() { 36 | @Override 37 | public boolean isZoomPressed() { 38 | return implementation.isZoomPressed(); 39 | } 40 | 41 | @Override 42 | public boolean isZoomInPressed() { 43 | return implementation.isZoomInPressed(); 44 | } 45 | 46 | @Override 47 | public boolean isZoomOutPressed() { 48 | return implementation.isZoomOutPressed(); 49 | } 50 | 51 | @Override 52 | public CameraPerspective getCameraPerspective() { 53 | return CameraPerspective.values()[implementation.getCameraPerspective().ordinal()]; 54 | } 55 | 56 | @Override 57 | public void onZoomActivate() { 58 | implementation.onZoomActivate(); 59 | } 60 | }, instanceConfigPath); 61 | } 62 | 63 | //region Query Methods 64 | /** 65 | * Returns `true` if Zoom is activated. 66 | */ 67 | @Contract(pure = true) 68 | public static boolean isActive() { 69 | return Zume.isActive(); 70 | } 71 | 72 | /** 73 | * Returns `true` if FOV should be hooked. 74 | */ 75 | @Contract(pure = true) 76 | public static boolean isFOVHookActive() { 77 | return Zume.isFOVHookActive(); 78 | } 79 | 80 | /** 81 | * Returns `true` if mouse scrolling should be hooked. 82 | */ 83 | @Contract(pure = true) 84 | public static boolean isMouseScrollHookActive() { 85 | return Zume.isMouseScrollHookActive(); 86 | } 87 | //endregion 88 | 89 | //region Hooks 90 | /** 91 | * This should be invoked once at the beginning of every frame. 92 | * It will handle Keybindings and Zoom Scrolling if the other hooks in this API are used correctly. 93 | */ 94 | public static void renderHook() { 95 | Zume.renderHook(); 96 | } 97 | 98 | /** 99 | * ONLY INVOKE THIS METHOD WHEN {@linkplain ZumeAPI#isFOVHookActive()} RETURNS `true`. 100 | * That check was explicitly excluded from this method for efficiency and for mixin compatibility. 101 | * The {@linkplain IZumeImplementation} is responsible for this check. 102 | * 103 | * @param fov The unmodified FOV value 104 | * {@return The new FOV transformed by Zume} 105 | */ 106 | public static double fovHook(double fov) { 107 | return Zume.fovHook(fov); 108 | } 109 | 110 | /** 111 | * This method assumes Zume is active and the camera perspective is third-person. 112 | * If it is not, using this value will cause bugs. 113 | * 114 | * @param distance The vanilla third-person camera distance 115 | * @return The new third-person camera distance 116 | */ 117 | public static double thirdPersonCameraHook(double distance) { 118 | return Zume.thirdPersonCameraHook(distance); 119 | } 120 | 121 | /** 122 | * The return value of this method can be safely used without checking whether Zume is active. 123 | * 124 | * @param mouseSensitivity The unmodified mouse sensitivity 125 | * {@return The new mouse sensitivity, transformed by Zume} 126 | */ 127 | public static double mouseSensitivityHook(double mouseSensitivity) { 128 | return Zume.mouseSensitivityHook(mouseSensitivity); 129 | } 130 | 131 | /** 132 | * The return value of this method can be safely used without checking whether Zume is active. 133 | * 134 | * @param cinematicCameraEnabled The unmodified cinematic camera state 135 | * {@return The new cinematic camera state, transformed by Zume} 136 | */ 137 | public static boolean cinematicCameraEnabledHook(boolean cinematicCameraEnabled) { 138 | return Zume.cinematicCameraEnabledHook(cinematicCameraEnabled); 139 | } 140 | 141 | /** 142 | * The return value of this method can be safely used without checking whether Zume is active. 143 | * 144 | * @param scrollDelta The scroll delta (magnitude will be ignored, only the sign is used) 145 | * {@return `true` if the invoker should prevent further handling of this scroll event} 146 | */ 147 | public static boolean mouseScrollHook(int scrollDelta) { 148 | return Zume.mouseScrollHook(scrollDelta); 149 | } 150 | //endregion 151 | 152 | } 153 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/CameraPerspective.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.impl; 2 | 3 | public enum CameraPerspective { 4 | 5 | FIRST_PERSON, 6 | THIRD_PERSON, 7 | THIRD_PERSON_FLIPPED, 8 | 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/EasedDouble.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.impl; 2 | 3 | import dev.nolij.libnolij.util.EasingUtil; 4 | 5 | public class EasedDouble { 6 | 7 | private short duration; 8 | private double inverseDuration; 9 | 10 | private double exponent; 11 | 12 | private double fromValue; 13 | private double targetValue; 14 | 15 | private long startTimestamp = 0L; 16 | private long endTimestamp = 0L; 17 | 18 | 19 | public EasedDouble(final double value) { 20 | this.targetValue = value; 21 | } 22 | 23 | public void update(final short duration, final double exponent) { 24 | this.duration = duration; 25 | this.inverseDuration = 1D / duration; 26 | this.exponent = exponent; 27 | } 28 | 29 | public double getEased() { 30 | if (isEasing()) { 31 | final long delta = System.currentTimeMillis() - startTimestamp; 32 | 33 | return EasingUtil.in(fromValue, targetValue, delta * inverseDuration, exponent); 34 | } 35 | 36 | return targetValue; 37 | } 38 | 39 | public double getTarget() { 40 | return targetValue; 41 | } 42 | 43 | public void setInstant(double target) { 44 | this.startTimestamp = 0L; 45 | this.endTimestamp = 0L; 46 | this.fromValue = 0D; 47 | this.targetValue = target; 48 | } 49 | 50 | public void set(double from, double target) { 51 | if (duration == 0) { 52 | setInstant(target); 53 | return; 54 | } 55 | 56 | this.startTimestamp = System.currentTimeMillis(); 57 | this.endTimestamp = startTimestamp + duration; 58 | this.fromValue = from; 59 | this.targetValue = target; 60 | } 61 | 62 | public void set(double target) { 63 | set(getEased(), target); 64 | } 65 | 66 | public boolean isEasing() { 67 | return System.currentTimeMillis() < endTimestamp; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/HostPlatform.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.impl; 2 | 3 | public enum HostPlatform { 4 | 5 | LINUX, 6 | WINDOWS, 7 | MAC_OS, 8 | UNKNOWN, 9 | 10 | } 11 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/IZumeImplementation.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.impl; 2 | 3 | public interface IZumeImplementation { 4 | 5 | boolean isZoomPressed(); 6 | boolean isZoomInPressed(); 7 | boolean isZoomOutPressed(); 8 | 9 | CameraPerspective getCameraPerspective(); 10 | 11 | default void onZoomActivate() {} 12 | 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/config/FileWatcher.java: -------------------------------------------------------------------------------- 1 | // Based off of https://gist.githubusercontent.com/danielflower/f54c2fe42d32356301c68860a4ab21ed/raw/d09c312b4e40b17cdce310992da89dc06aabb98a/FileWatcher.java 2 | // Original License (all modifications are still distributed under this project's license): https://gist.github.com/danielflower/f54c2fe42d32356301c68860a4ab21ed?permalink_comment_id=2352260#gistcomment-2352260 3 | 4 | package dev.nolij.zume.impl.config; 5 | 6 | import dev.nolij.zume.impl.Zume; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.*; 10 | import java.util.concurrent.Semaphore; 11 | 12 | public class FileWatcher implements IFileWatcher { 13 | 14 | private static final long DEBOUNCE_DURATION_MS = 500L; 15 | 16 | /** 17 | * Starts watching a file and the given path and calls the callback when it is changed. 18 | * A shutdown hook is registered to stop watching. To control this yourself, create an 19 | * instance and use the start/stop methods. 20 | */ 21 | public static FileWatcher onFileChange(Path file, Runnable callback) throws IOException { 22 | final FileWatcher watcher = new FileWatcher(); 23 | watcher.start(file, callback); 24 | 25 | return watcher; 26 | } 27 | 28 | private WatchService watchService; 29 | private Thread thread; 30 | private long debounce = 0L; 31 | private final Semaphore semaphore = new Semaphore(1); 32 | 33 | @Override 34 | public void lock() throws InterruptedException { 35 | synchronized (semaphore) { 36 | semaphore.acquire(); 37 | } 38 | } 39 | 40 | @Override 41 | public boolean tryLock() { 42 | synchronized (semaphore) { 43 | return semaphore.tryAcquire(); 44 | } 45 | } 46 | 47 | @Override 48 | public void unlock() { 49 | synchronized (semaphore) { 50 | if (semaphore.availablePermits() > 0) 51 | return; 52 | 53 | semaphore.release(); 54 | } 55 | } 56 | 57 | public void start(Path file, Runnable callback) throws IOException { 58 | watchService = FileSystems.getDefault().newWatchService(); 59 | Path parent = file.getParent(); 60 | parent.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE); 61 | 62 | thread = new Thread(() -> { 63 | while (true) { 64 | WatchKey wk = null; 65 | try { 66 | wk = watchService.take(); 67 | for (final WatchEvent event : wk.pollEvents()) { 68 | final Path changed = parent.resolve((Path) event.context()); 69 | boolean locked = false; 70 | try { 71 | if ((locked = tryLock()) && 72 | System.currentTimeMillis() > debounce && 73 | Files.exists(changed) && 74 | Files.isSameFile(changed, file)) { 75 | callback.run(); 76 | debounce = System.currentTimeMillis() + DEBOUNCE_DURATION_MS; 77 | break; 78 | } 79 | } catch (NoSuchFileException ignored) { 80 | } catch (IOException e) { 81 | Zume.LOGGER.error("Error in config watcher: ", e); 82 | } finally { 83 | if (locked) 84 | unlock(); 85 | } 86 | } 87 | } catch (InterruptedException e) { 88 | Thread.currentThread().interrupt(); 89 | break; 90 | } finally { 91 | if (wk != null) { 92 | wk.reset(); 93 | } 94 | } 95 | } 96 | }); 97 | thread.setDaemon(true); 98 | thread.start(); 99 | } 100 | 101 | public void stop() { 102 | thread.interrupt(); 103 | watchService.close(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/config/IFileWatcher.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.impl.config; 2 | 3 | public interface IFileWatcher { 4 | 5 | void lock() throws InterruptedException; 6 | boolean tryLock(); 7 | void unlock(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/config/NullFileWatcher.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.impl.config; 2 | 3 | public final class NullFileWatcher implements IFileWatcher { 4 | 5 | @Override 6 | public void lock() {} 7 | 8 | @Override 9 | public boolean tryLock() { 10 | return true; 11 | } 12 | 13 | @Override 14 | public void unlock() {} 15 | 16 | } 17 | -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/config/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package dev.nolij.zume.impl.config; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /api/src/main/java/dev/nolij/zume/impl/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package dev.nolij.zume.impl; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /api/src/main/resources/assets/zume/lang/en_US.lang: -------------------------------------------------------------------------------- 1 | zume=Zume 2 | zume.zoom=Zoom 3 | zume.zoom_in=Zoom In 4 | zume.zoom_out=Zoom Out -------------------------------------------------------------------------------- /api/src/main/resources/assets/zume/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "zume": "Zume", 3 | "zume.zoom": "Zoom", 4 | "zume.zoom_in": "Zoom In", 5 | "zume.zoom_out": "Zoom Out", 6 | "zume.instant": "Instant", 7 | "zume.disabled": "Disabled", 8 | "zume.blocks": "%d block(s)", 9 | "zume.linear": "Linear", 10 | "zume.quad": "Quadratic", 11 | "zume.cubic": "Cubic", 12 | "zume.quart": "Quartic", 13 | "zume.quint": "Quintic", 14 | "zume.options.pages.general": "General", 15 | "zume.options.pages.advanced": "Advanced", 16 | "zume.options.zoom_speed.name": "Zoom Speed", 17 | "zume.options.zoom_speed.tooltip": "Speed for Zoom In/Out key binds & zoom scrolling (if enabled).\nDEFAULT: 20", 18 | "zume.options.enable_zoom_scrolling.name": "Enable Zoom Scrolling", 19 | "zume.options.enable_zoom_scrolling.tooltip": "Allows you to zoom in and out by scrolling up and down on your mouse while zoom is active.\nThis will prevent you from scrolling through your hotbar while zooming if enabled.\nDEFAULT: Enabled", 20 | "zume.options.enable_cinematic_zoom.name": "Enable Cinematic Zoom", 21 | "zume.options.enable_cinematic_zoom.tooltip": "Enable Cinematic Camera while zooming.\nDEFAULT: Enabled", 22 | "zume.options.mouse_sensitivity_floor.name": "Mouse Sensitivity Floor", 23 | "zume.options.mouse_sensitivity_floor.tooltip": "Mouse Sensitivity will not be reduced below this amount while zoomed in.\nDEFAULT: 40%", 24 | "zume.options.default_zoom.name": "Default Zoom", 25 | "zume.options.default_zoom.tooltip": "Default starting zoom percentage.\nDEFAULT: 50%", 26 | "zume.options.first_person_toggle_mode.name": "First-Person Toggle Mode", 27 | "zume.options.first_person_toggle_mode.tooltip": "If enabled, the Zoom keybind will act as a toggle in first-person.\nIf disabled, Zoom will only be active in first-person while the keybind is held.\nDEFAULT: Disabled", 28 | "zume.options.third_person_toggle_mode.name": "Third-Person Toggle Mode", 29 | "zume.options.third_person_toggle_mode.tooltip": "If enabled, the Zoom keybind will act as a toggle in third-person.\nIf disabled, Zoom will only be active in third-person while the keybind is held.\nDEFAULT: Enabled", 30 | "zume.options.zoom_smoothness_ms.name": "Zoom Smoothness", 31 | "zume.options.zoom_smoothness_ms.tooltip": "FOV changes will be spread out over this many milliseconds.\nDEFAULT: 150ms", 32 | "zume.options.max_third_person_zoom_blocks.name": "Maximum Third-Person Zoom Distance", 33 | "zume.options.max_third_person_zoom_blocks.tooltip": "Maximum third-person zoom distance (in blocks).\nDEFAULT: 15 blocks", 34 | "zume.options.min_third_person_zoom_blocks.name": "Minimum Third-Person Zoom Distance", 35 | "zume.options.min_third_person_zoom_blocks.tooltip": "Minimum third-person zoom distance (in blocks).\nSet to 4 blocks to mimic vanilla.\nDEFAULT: 0.5 blocks", 36 | "zume.options.animation_easing_exponent.name": "Animation Easing Exponent", 37 | "zume.options.animation_easing_exponent.tooltip": "The exponent used for easing animations.\nDEFAULT: Quartic", 38 | "zume.options.zoom_easing_exponent.name": "Zoom Normalizing Exponent", 39 | "zume.options.zoom_easing_exponent.tooltip": "The exponent used for making differences in FOV more uniform.\nDEFAULT: Quadratic", 40 | "zume.options.min_fov.name": "Minimum FOV", 41 | "zume.options.min_fov.tooltip": "Minimum zoom FOV.\nDEFAULT: 1.0", 42 | "zume.options.disable.name": "Disable", 43 | "zume.options.disable.tooltip": "If enabled, the mod will be disabled (on some platforms, key binds will still show in game options; they won't do anything if this is set to enabled).\nRequires re-launch to take effect.\nDEFAULT: Disabled" 44 | } -------------------------------------------------------------------------------- /api/src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/Zume/1b8db932dd5fa62dd89a096638bd0f78baa8e6f0/api/src/main/resources/icon.png -------------------------------------------------------------------------------- /api/src/main/resources/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/Zume/1b8db932dd5fa62dd89a096638bd0f78baa8e6f0/api/src/main/resources/icon.xcf -------------------------------------------------------------------------------- /archaic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 2 | 3 | unimined.minecraft { 4 | version("archaic_minecraft_version"()) 5 | 6 | minecraftForge { 7 | loader("archaic_forge_version"()) 8 | mixinConfig("zume-${project.name}.mixins.json") 9 | } 10 | 11 | mappings { 12 | searge() 13 | mcp("stable", "archaic_mappings_version"()) 14 | } 15 | } 16 | 17 | dependencies { 18 | compileOnly(project(":stubs")) 19 | 20 | "modImplementation"("com.github.LegacyModdingMC.UniMixins:unimixins-all-1.7.10:${"unimixins_version"()}:dev") 21 | } -------------------------------------------------------------------------------- /archaic/src/main/java/dev/nolij/zume/archaic/ArchaicConfigProvider.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.archaic; 2 | 3 | import net.minecraft.client.gui.GuiScreen; 4 | import net.minecraftforge.client.gui.ForgeGuiFactory; 5 | 6 | @SuppressWarnings("unused") 7 | public class ArchaicConfigProvider extends ForgeGuiFactory { 8 | 9 | @Override 10 | public Class mainConfigGuiClass() { 11 | return ArchaicZumeConfigGUI.class; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /archaic/src/main/java/dev/nolij/zume/archaic/ArchaicZume.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.archaic; 2 | 3 | import cpw.mods.fml.common.eventhandler.Event; 4 | import cpw.mods.fml.common.eventhandler.EventPriority; 5 | import cpw.mods.fml.common.eventhandler.SubscribeEvent; 6 | import cpw.mods.fml.relauncher.FMLLaunchHandler; 7 | import dev.nolij.libnolij.refraction.Refraction; 8 | import dev.nolij.zume.impl.CameraPerspective; 9 | import dev.nolij.zume.impl.IZumeImplementation; 10 | import dev.nolij.zume.impl.Zume; 11 | import dev.nolij.zume.mixin.archaic.EntityRendererAccessor; 12 | import cpw.mods.fml.client.registry.ClientRegistry; 13 | import cpw.mods.fml.common.Mod; 14 | import cpw.mods.fml.common.event.FMLPreInitializationEvent; 15 | import net.minecraft.client.Minecraft; 16 | import net.minecraft.launchwrapper.Launch; 17 | import net.minecraft.util.MouseFilter; 18 | import net.minecraftforge.client.event.MouseEvent; 19 | import net.minecraftforge.common.MinecraftForge; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodType; 23 | 24 | import static dev.nolij.zume.impl.ZumeConstants.*; 25 | 26 | @Mod( 27 | modid = MOD_ID, 28 | name = MOD_NAME, 29 | version = MOD_VERSION, 30 | acceptedMinecraftVersions = ARCHAIC_VERSION_RANGE, 31 | guiFactory = "dev.nolij.zume.archaic.ArchaicConfigProvider", 32 | dependencies = "required-after:unimixins@[0.1.15,)") 33 | public class ArchaicZume implements IZumeImplementation { 34 | 35 | @Mod.EventHandler 36 | public void preInit(FMLPreInitializationEvent event) { 37 | if (!FMLLaunchHandler.side().isClient()) 38 | return; 39 | 40 | Zume.LOGGER.info("Loading Archaic Zume..."); 41 | 42 | Zume.registerImplementation(this, Launch.minecraftHome.toPath().resolve("config")); 43 | if (Zume.disabled) 44 | return; 45 | 46 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 47 | ClientRegistry.registerKeyBinding(keyBind.value); 48 | } 49 | 50 | MinecraftForge.EVENT_BUS.register(this); 51 | 52 | Zume.postInit(); 53 | } 54 | 55 | @Override 56 | public boolean isZoomPressed() { 57 | return Minecraft.getMinecraft().currentScreen == null && ZumeKeyBind.ZOOM.isPressed(); 58 | } 59 | 60 | @Override 61 | public boolean isZoomInPressed() { 62 | return ZumeKeyBind.ZOOM_IN.isPressed(); 63 | } 64 | 65 | @Override 66 | public boolean isZoomOutPressed() { 67 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 68 | } 69 | 70 | @Override 71 | public CameraPerspective getCameraPerspective() { 72 | return CameraPerspective.values()[Minecraft.getMinecraft().gameSettings.thirdPersonView]; 73 | } 74 | 75 | @Override 76 | public void onZoomActivate() { 77 | if (Zume.config.enableCinematicZoom && !Minecraft.getMinecraft().gameSettings.smoothCamera) { 78 | final EntityRendererAccessor entityRenderer = (EntityRendererAccessor) Minecraft.getMinecraft().entityRenderer; 79 | entityRenderer.setMouseFilterXAxis(new MouseFilter()); 80 | entityRenderer.setMouseFilterYAxis(new MouseFilter()); 81 | entityRenderer.setSmoothCamYaw(0F); 82 | entityRenderer.setSmoothCamPitch(0F); 83 | entityRenderer.setSmoothCamFilterX(0F); 84 | entityRenderer.setSmoothCamFilterY(0F); 85 | entityRenderer.setSmoothCamPartialTicks(0F); 86 | } 87 | } 88 | 89 | private static final MethodHandle SET_CANCELED = Refraction.safe().getMethodOrNull( 90 | Event.class, 91 | "setCanceled", 92 | MethodType.methodType(void.class, MouseEvent.class, boolean.class), 93 | boolean.class 94 | ); 95 | 96 | @SubscribeEvent(priority = EventPriority.HIGHEST) 97 | public void mouseEvent(MouseEvent mouseEvent) { 98 | if (Zume.mouseScrollHook(mouseEvent.dwheel)) { 99 | //noinspection DataFlowIssue 100 | SET_CANCELED.invokeExact(mouseEvent, true); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /archaic/src/main/java/dev/nolij/zume/archaic/ArchaicZumeConfigGUI.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.archaic; 2 | 3 | import cpw.mods.fml.client.config.GuiConfig; 4 | import dev.nolij.zume.impl.Zume; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.GuiScreen; 7 | 8 | import java.util.Collections; 9 | 10 | import static dev.nolij.zume.impl.ZumeConstants.MOD_ID; 11 | 12 | public class ArchaicZumeConfigGUI extends GuiConfig { 13 | 14 | public ArchaicZumeConfigGUI(GuiScreen parentScreen) { 15 | super(parentScreen, Collections.emptyList(), MOD_ID, false, false, "config"); 16 | 17 | Zume.openConfigFile(); 18 | } 19 | 20 | @Override 21 | public void initGui() { 22 | Minecraft.getMinecraft().displayGuiScreen(parentScreen); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /archaic/src/main/java/dev/nolij/zume/archaic/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.archaic; 2 | 3 | import net.minecraft.client.settings.KeyBinding; 4 | import org.lwjgl.input.Keyboard; 5 | 6 | public enum ZumeKeyBind { 7 | 8 | ZOOM("zume.zoom", Keyboard.KEY_Z), 9 | ZOOM_IN("zume.zoom_in", Keyboard.KEY_EQUALS), 10 | ZOOM_OUT("zume.zoom_out", Keyboard.KEY_MINUS), 11 | 12 | ; 13 | 14 | public final KeyBinding value; 15 | 16 | public boolean isPressed() { 17 | return value.getIsKeyPressed(); 18 | } 19 | 20 | ZumeKeyBind(String translationKey, int code) { 21 | this.value = new KeyBinding(translationKey, code, "zume"); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /archaic/src/main/java/dev/nolij/zume/mixin/archaic/EntityRendererAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.archaic; 2 | 3 | import net.minecraft.client.renderer.EntityRenderer; 4 | import net.minecraft.util.MouseFilter; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(EntityRenderer.class) 9 | public interface EntityRendererAccessor { 10 | 11 | @Accessor("smoothCamFilterX") 12 | void setSmoothCamFilterX(float value); 13 | @Accessor("smoothCamFilterY") 14 | void setSmoothCamFilterY(float value); 15 | @Accessor("smoothCamYaw") 16 | void setSmoothCamYaw(float value); 17 | @Accessor("smoothCamPitch") 18 | void setSmoothCamPitch(float value); 19 | @Accessor("smoothCamPartialTicks") 20 | void setSmoothCamPartialTicks(float value); 21 | @Accessor("mouseFilterXAxis") 22 | void setMouseFilterXAxis(MouseFilter value); 23 | @Accessor("mouseFilterYAxis") 24 | void setMouseFilterYAxis(MouseFilter value); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /archaic/src/main/java/dev/nolij/zume/mixin/archaic/EntityRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.archaic; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 5 | import dev.nolij.zume.impl.Zume; 6 | import net.minecraft.client.renderer.EntityRenderer; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(EntityRenderer.class) 13 | public abstract class EntityRendererMixin { 14 | 15 | @Inject(method = "updateCameraAndRender", at = @At("HEAD")) 16 | public void zume$render$HEAD(CallbackInfo ci) { 17 | Zume.renderHook(); 18 | } 19 | 20 | @ModifyReturnValue(method = "getFOVModifier", at = @At("TAIL")) 21 | public float zume$getFOV$TAIL(float original) { 22 | if (Zume.isFOVHookActive()) 23 | return (float) Zume.fovHook(original); 24 | 25 | return original; 26 | } 27 | 28 | @ModifyExpressionValue(method = {"updateCameraAndRender", "updateRenderer"}, 29 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;smoothCamera:Z")) 30 | public boolean zume$updateMouse$smoothCameraEnabled(boolean original) { 31 | return Zume.cinematicCameraEnabledHook(original); 32 | } 33 | 34 | @ModifyExpressionValue(method = {"updateCameraAndRender", "updateRenderer"}, 35 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;mouseSensitivity:F")) 36 | public float zume$updateMouse$mouseSensitivity(float original) { 37 | return (float) Zume.mouseSensitivityHook(original); 38 | } 39 | 40 | @ModifyExpressionValue(method = "orientCamera", 41 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/EntityRenderer;thirdPersonDistance:F")) 42 | public float zume$orientCamera$thirdPersonDistance(float original) { 43 | return (float) Zume.thirdPersonCameraHook(original); 44 | } 45 | 46 | @ModifyExpressionValue(method = "orientCamera", 47 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/EntityRenderer;thirdPersonDistanceTemp:F")) 48 | public float zume$orientCamera$thirdPersonDistanceTemp(float original) { 49 | return (float) Zume.thirdPersonCameraHook(original); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /archaic/src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "zume", 4 | "name": "${mod_name}", 5 | "description": "${mod_description}", 6 | "version": "${mod_version}", 7 | "mcversion": "${archaic_minecraft_version}", 8 | "url": "${mod_url}", 9 | "updateUrl": "", 10 | "authorList": ["${nolij}"], 11 | "credits": "", 12 | "logoFile": "icon_large.png", 13 | "screenshots": [], 14 | "dependencies": [] 15 | } 16 | ] -------------------------------------------------------------------------------- /archaic/src/main/resources/zume-archaic.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "archaic.EntityRendererAccessor", 8 | "archaic.EntityRendererMixin" 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | }, 13 | "refmap": "zume-archaic-refmap.json" 14 | } 15 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | 3 | plugins { 4 | id("idea") 5 | `kotlin-dsl` 6 | } 7 | 8 | idea.module { 9 | isDownloadJavadoc = true 10 | isDownloadSources = true 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | maven("https://maven.wagyourtail.xyz/releases") 16 | maven("https://maven.wagyourtail.xyz/snapshots") 17 | gradlePluginPortal { 18 | content { 19 | excludeGroup("org.apache.logging.log4j") 20 | } 21 | } 22 | } 23 | 24 | kotlin { 25 | jvmToolchain { 26 | languageVersion = JavaLanguageVersion.of(21) 27 | } 28 | } 29 | 30 | fun DependencyHandler.plugin(id: String, version: String) = 31 | this.implementation(group = id, name = "$id.gradle.plugin", version = version) 32 | 33 | val gradleProperties = Properties().apply { 34 | load(rootDir.parentFile.resolve("gradle.properties").inputStream()) 35 | } 36 | 37 | operator fun String.invoke(): String = gradleProperties.getProperty(this) ?: error("Property $this not found") 38 | 39 | dependencies { 40 | implementation("org.ow2.asm:asm-tree:${"asm_version"()}") 41 | implementation("net.fabricmc:mapping-io:${"mapping_io_version"()}") 42 | implementation("org.apache.ant:ant:${"shadow_ant_version"()}") 43 | implementation("com.guardsquare:proguard-base:${"proguard_version"()}") 44 | 45 | plugin(id = "com.gradleup.shadow", version = "shadow_version"()) 46 | plugin(id = "xyz.wagyourtail.unimined", version = "unimined_version"()) 47 | plugin(id = "com.github.gmazzo.buildconfig", version = "buildconfig_version"()) 48 | plugin(id = "org.ajoberstar.grgit", version = "grgit_version"()) 49 | plugin(id = "me.modmuss50.mod-publish-plugin", version = "mod_publish_version"()) 50 | plugin(id = "ru.vyarus.use-python", version = "use_python_version"()) 51 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Zume.kt: -------------------------------------------------------------------------------- 1 | @Suppress("MemberVisibilityCanBePrivate") 2 | object Zume { 3 | private var _version: String? = null 4 | var version: String = "" 5 | get() = _version!! 6 | set(value) { 7 | field = value 8 | if (value.isNotEmpty()) 9 | _version = value 10 | } 11 | 12 | var auditAndExitEnabled = false 13 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/DeflateAlgorithm.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle 2 | 3 | import xyz.wagyourtail.unimined.util.capitalized 4 | 5 | enum class DeflateAlgorithm(val id: Int) { 6 | 7 | /** 8 | * Entries are stored without compression 9 | */ 10 | STORE(0), 11 | 12 | /** 13 | * Entries are stored with Zlib 14 | */ 15 | FAST(1), 16 | 17 | /** 18 | * Entries are stored with libdeflate 19 | */ 20 | NORMAL(2), 21 | 22 | /** 23 | * Entries are stored with 7zip 24 | */ 25 | EXTRA(3), 26 | 27 | /** 28 | * Entries are stored with Zopfli 29 | */ 30 | INSANE(4); 31 | 32 | override fun toString() = name.lowercase().capitalized() 33 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/JsonShrinkingType.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle 2 | 3 | enum class JsonShrinkingType { 4 | MINIFY, PRETTY_PRINT 5 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/MixinConfigMergingTransformer.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer 4 | import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext 5 | import groovy.json.JsonOutput 6 | import groovy.json.JsonSlurper 7 | import org.gradle.api.file.FileTreeElement 8 | import org.gradle.api.tasks.Input 9 | import org.apache.tools.zip.ZipOutputStream 10 | import org.apache.tools.zip.ZipEntry 11 | 12 | class MixinConfigMergingTransformer : Transformer { 13 | 14 | private val JSON = JsonSlurper() 15 | 16 | @Input 17 | lateinit var modId: String 18 | @Input 19 | lateinit var packageName: String 20 | @Input 21 | lateinit var mixinPlugin: String 22 | 23 | override fun getName(): String { 24 | return "MixinConfigMergingTransformer" 25 | } 26 | 27 | override fun canTransformResource(element: FileTreeElement?): Boolean { 28 | return element != null && (element.name.endsWith(".mixins.json") || element.name.endsWith("-refmap.json")) 29 | } 30 | 31 | private var transformed = false 32 | 33 | private var mixins = ArrayList() 34 | private var refMaps = HashMap>() 35 | 36 | override fun transform(context: TransformerContext?) { 37 | if (context == null) 38 | return 39 | 40 | this.transformed = true 41 | 42 | val parsed = JSON.parse(context.`is`) as Map<*, *> 43 | if (parsed.contains("client")) { 44 | @Suppress("UNCHECKED_CAST") 45 | mixins.addAll(parsed["client"] as List) 46 | } else { 47 | @Suppress("UNCHECKED_CAST") 48 | refMaps.putAll(parsed["mappings"] as Map>) 49 | } 50 | } 51 | 52 | override fun hasTransformedResource(): Boolean { 53 | return transformed 54 | } 55 | 56 | override fun modifyOutputStream(os: ZipOutputStream?, preserveFileTimestamps: Boolean) { 57 | val mixinConfigEntry = ZipEntry("${modId}.mixins.json") 58 | os!!.putNextEntry(mixinConfigEntry) 59 | 60 | val mixinConfigJson = mutableMapOf( 61 | "required" to true, 62 | "minVersion" to "0.8", 63 | "package" to packageName, 64 | "plugin" to mixinPlugin, 65 | "compatibilityLevel" to "JAVA_8", 66 | "client" to mixins, 67 | "injectors" to mapOf( 68 | "defaultRequire" to 1, 69 | ) 70 | ) 71 | 72 | if(refMaps.isNotEmpty()) { 73 | val refmapName = "${modId}-refmap.json" 74 | mixinConfigJson["refmap"] = refmapName 75 | os.putNextEntry(ZipEntry(refmapName)) 76 | os.write( 77 | JsonOutput.prettyPrint( 78 | JsonOutput.toJson( 79 | mapOf( 80 | "mappings" to refMaps, 81 | ) 82 | ) 83 | ).toByteArray() 84 | ) 85 | } 86 | 87 | os.write(JsonOutput.prettyPrint(JsonOutput.toJson(mixinConfigJson)).toByteArray()) 88 | 89 | transformed = false 90 | mixins.clear() 91 | refMaps.clear() 92 | } 93 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/entryprocessing/EntryProcessors.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.entryprocessing 2 | 3 | import dev.nolij.zumegradle.util.map 4 | import dev.nolij.zumegradle.util.mappings 5 | import groovy.json.JsonOutput 6 | import groovy.json.JsonSlurper 7 | import net.fabricmc.mappingio.format.MappingFormat 8 | import org.objectweb.asm.tree.AnnotationNode 9 | import java.io.File 10 | 11 | import dev.nolij.zumegradle.util.toBytes 12 | import dev.nolij.zumegradle.util.ClassNode 13 | import org.objectweb.asm.tree.ClassNode 14 | 15 | typealias EntryProcessor = (String, ByteArray) -> ByteArray 16 | 17 | object EntryProcessors { 18 | val PASS: EntryProcessor = { _, bytes -> bytes } 19 | 20 | fun jsonMinifier(shouldRun: (String) -> Boolean = { it.endsWith(".json") }): EntryProcessor = { name, bytes -> 21 | if (shouldRun(name)) { 22 | JsonOutput.toJson(JsonSlurper().parse(bytes)).toByteArray() 23 | } else { 24 | bytes 25 | } 26 | } 27 | 28 | fun jsonPrettyPrinter(shouldRun: (String) -> Boolean = { it.endsWith(".json") }): EntryProcessor = { name, bytes -> 29 | if (shouldRun(name)) { 30 | JsonOutput.prettyPrint(String(bytes)).toByteArray() 31 | } else { 32 | bytes 33 | } 34 | } 35 | 36 | @Suppress("UNCHECKED_CAST") 37 | fun obfuscationFixer(mappingsFile: File, format: MappingFormat = MappingFormat.PROGUARD): EntryProcessor = { name, bytes -> 38 | val mappings = mappings(mappingsFile, format) 39 | if (name.endsWith("mixins.json")) { 40 | val prettyPrinted = String(bytes).contains("\n") // probably 41 | val json = (JsonSlurper().parse(bytes) as Map).toMutableMap() 42 | json["plugin"] = mappings.map(json["plugin"] as String) 43 | 44 | json["package"] = "zume.mixin" // TODO: make this configurable 45 | 46 | val result = JsonOutput.toJson(json) 47 | if (prettyPrinted) { 48 | JsonOutput.prettyPrint(result).toByteArray() 49 | } else { 50 | result.toByteArray() 51 | } 52 | } else if (name.endsWith(".class")) { 53 | val classNode = ClassNode(bytes) 54 | 55 | for (annotation in classNode.visibleAnnotations ?: emptyList()) { 56 | if (annotation.desc.endsWith("fml/common/Mod;")) { 57 | for (i in 0 until annotation.values.size step 2) { 58 | if (annotation.values[i] == "guiFactory") { 59 | val old = annotation.values[i + 1] as String 60 | annotation.values[i + 1] = mappings.map(old) 61 | println("Remapped guiFactory: $old -> ${annotation.values[i + 1]}") 62 | } 63 | } 64 | } 65 | } 66 | 67 | classNode.toBytes() 68 | } else { 69 | bytes 70 | } 71 | } 72 | 73 | fun modifyClass(modifier: (ClassNode) -> Unit): EntryProcessor = a@ { name, bytes -> 74 | if (!name.endsWith(".class")) { 75 | return@a bytes 76 | } 77 | 78 | ClassNode(bytes).apply(modifier).toBytes() 79 | } 80 | 81 | fun removeAnnotations(extraAnnotationsToStrip: (AnnotationNode) -> Boolean): EntryProcessor = modifyClass { classNode -> 82 | val shouldStripAnnotation: (AnnotationNode) -> Boolean = { 83 | setOf( 84 | "Lorg/spongepowered/asm/mixin/Dynamic;", 85 | "Lorg/spongepowered/asm/mixin/Final;", 86 | "Ljava/lang/SafeVarargs;", 87 | ).contains(it.desc) 88 | || it.desc.startsWith("Lorg/jetbrains/annotations/") 89 | || extraAnnotationsToStrip(it) 90 | } 91 | 92 | classNode.invisibleAnnotations?.removeIf(shouldStripAnnotation) 93 | classNode.visibleAnnotations?.removeIf(shouldStripAnnotation) 94 | classNode.invisibleTypeAnnotations?.removeIf(shouldStripAnnotation) 95 | classNode.visibleTypeAnnotations?.removeIf(shouldStripAnnotation) 96 | classNode.fields.forEach { 97 | it.invisibleAnnotations?.removeIf(shouldStripAnnotation) 98 | it.visibleAnnotations?.removeIf(shouldStripAnnotation) 99 | it.invisibleTypeAnnotations?.removeIf(shouldStripAnnotation) 100 | it.visibleTypeAnnotations?.removeIf(shouldStripAnnotation) 101 | } 102 | classNode.methods.forEach { 103 | it.invisibleAnnotations?.removeIf(shouldStripAnnotation) 104 | it.visibleAnnotations?.removeIf(shouldStripAnnotation) 105 | it.invisibleTypeAnnotations?.removeIf(shouldStripAnnotation) 106 | it.visibleTypeAnnotations?.removeIf(shouldStripAnnotation) 107 | it.invisibleLocalVariableAnnotations?.removeIf(shouldStripAnnotation) 108 | it.visibleLocalVariableAnnotations?.removeIf(shouldStripAnnotation) 109 | it.invisibleParameterAnnotations?.forEach { parameterAnnotations -> 110 | parameterAnnotations?.removeIf(shouldStripAnnotation) 111 | } 112 | it.visibleParameterAnnotations?.forEach { parameterAnnotations -> 113 | parameterAnnotations?.removeIf(shouldStripAnnotation) 114 | } 115 | } 116 | 117 | if (classNode.invisibleAnnotations?.any { it.desc == "Lorg/spongepowered/asm/mixin/Mixin;" } == true) { 118 | // remove any empty constructors that just call super() 119 | // since these classes are never loaded, they are not needed 120 | // 3 instructions are ALOAD + call to super() + RETURN 121 | classNode.methods.removeAll { it.name == "" && it.instructions.size() <= 3 } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/smoketest/MojangMetaApi.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.smoketest 2 | 3 | import groovy.json.JsonSlurper 4 | import java.io.InputStream 5 | import java.net.HttpURLConnection 6 | import java.net.URI 7 | import java.util.concurrent.TimeUnit 8 | 9 | @Suppress("UNCHECKED_CAST") 10 | object MojangMetaApi { 11 | private const val MANIFEST = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" 12 | private val JSON = JsonSlurper() 13 | 14 | fun getJavaVersion(mcVersion: String): Int { 15 | if(mcVersion == "snapshot") { 16 | return getJavaVersion(getLatestSnapshot()) 17 | } else if(mcVersion == "release") { 18 | return getJavaVersion(getLatestRelease()) 19 | } 20 | val manifest = request(MANIFEST) as Map 21 | val versions = manifest["versions"] as List> 22 | val version = versions.firstOrNull { it["id"] == mcVersion } 23 | ?: error("invalid minecraft version: $mcVersion") 24 | 25 | val versionManifest = request(version["url"] as String) as Map 26 | val javaVersion = versionManifest["javaVersion"] as Map 27 | return javaVersion["majorVersion"] as Int 28 | } 29 | 30 | fun getLatestRelease(): String { 31 | val manifest = request(MANIFEST) as Map 32 | val latest = manifest["latest"] as Map 33 | return latest["release"]!! 34 | } 35 | 36 | fun getLatestSnapshot(): String { 37 | val manifest = request(MANIFEST) as Map 38 | val latest = manifest["latest"] as Map 39 | return latest["snapshot"]!! 40 | } 41 | 42 | private val cache = mutableMapOf() 43 | private fun request(url: String): Any { 44 | if(url in cache) 45 | return cache[url]!! 46 | 47 | val data = JSON.parseText(requestImpl(url).bufferedReader().readText()) 48 | cache[url] = data 49 | return data 50 | } 51 | 52 | private fun requestImpl(url: String): InputStream { 53 | val connection = URI(url).toURL().openConnection() as HttpURLConnection 54 | connection.requestMethod = "GET" 55 | connection.setRequestProperty("User-Agent", "ZumeGradle/${Zume.version} (https://github.com/Nolij/Zume)") 56 | connection.connectTimeout = TimeUnit.SECONDS.toMillis(10).toInt() 57 | connection.readTimeout = TimeUnit.SECONDS.toMillis(5).toInt() 58 | connection.connect() 59 | 60 | if (connection.responseCode != 200) 61 | error("Failed to request $url: ${connection.responseCode}") 62 | 63 | return connection.inputStream 64 | } 65 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/task/AdvzipTask.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.task 2 | 3 | import dev.nolij.zumegradle.DeflateAlgorithm 4 | import org.gradle.api.provider.Property 5 | import org.gradle.api.tasks.Input 6 | 7 | @Suppress("LeakingThis") 8 | abstract class AdvzipTask : ProcessJarTask() { 9 | @get:Input 10 | abstract val level: Property 11 | 12 | @get:Input 13 | abstract val throwIfNotInstalled: Property 14 | 15 | init { 16 | throwIfNotInstalled.convention(false) 17 | } 18 | 19 | override fun process() { 20 | if(try { 21 | ProcessBuilder("advzip", "-V").start().waitFor() != 0 22 | } catch (e: Exception) { true }) { 23 | if(throwIfNotInstalled.get()) { 24 | throw IllegalStateException("advzip is not installed") 25 | } 26 | 27 | println("advzip is not installed, skipping ${this.name}") 28 | return 29 | } 30 | 31 | val jar = inputJar.get().asFile.copyTo(archiveFile.get().asFile, true) 32 | val type = level.get() 33 | 34 | try { 35 | val process = ProcessBuilder("advzip", "-z", "-${type.id}", jar.absolutePath).start() 36 | val exitCode = process.waitFor() 37 | if (exitCode != 0) { 38 | error("Failed to compress $jar with $type") 39 | } 40 | } catch (e: Exception) { 41 | error("Failed to compress $jar with $type: ${e.message}") 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/task/CopyJarTask.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.task 2 | 3 | abstract class CopyJarTask : ProcessJarTask() { 4 | override fun process() { 5 | inputJar.get().asFile.copyTo(archiveFile.get().asFile, true) 6 | } 7 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/task/JarEntryModificationTask.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.task 2 | 3 | import dev.nolij.zumegradle.JsonShrinkingType 4 | import org.gradle.api.tasks.Input 5 | import dev.nolij.zumegradle.entryprocessing.EntryProcessor 6 | import dev.nolij.zumegradle.entryprocessing.EntryProcessors 7 | import org.gradle.api.provider.ListProperty 8 | import java.util.jar.JarEntry 9 | import java.util.jar.JarFile 10 | import java.util.jar.JarOutputStream 11 | import java.util.zip.Deflater 12 | 13 | abstract class JarEntryModificationTask : ProcessJarTask() { 14 | @get:Input 15 | protected abstract val processors: ListProperty 16 | 17 | fun process(processor: EntryProcessor) { 18 | processors.add(processor) 19 | } 20 | 21 | fun json(type: JsonShrinkingType?, shouldRun: (String) -> Boolean = { it.endsWith(".json") }) { 22 | processors.add(when(type) { 23 | null -> EntryProcessors.PASS 24 | JsonShrinkingType.MINIFY -> EntryProcessors.jsonMinifier(shouldRun) 25 | JsonShrinkingType.PRETTY_PRINT -> EntryProcessors.jsonPrettyPrinter(shouldRun) 26 | }) 27 | } 28 | 29 | override fun process() { 30 | val contents = linkedMapOf() 31 | JarFile(inputJar.get().asFile).use { 32 | it.entries().asIterator().forEach { entry -> 33 | if (!entry.isDirectory) { 34 | contents[entry.name] = it.getInputStream(entry).readAllBytes() 35 | } 36 | } 37 | } 38 | 39 | JarOutputStream(archiveFile.get().asFile.outputStream()).use { out -> 40 | out.setLevel(Deflater.BEST_COMPRESSION) 41 | contents.forEach { var (name, bytes) = it 42 | 43 | processors.get().forEach { processor -> 44 | bytes = processor(name, bytes) 45 | } 46 | 47 | out.putNextEntry(JarEntry(name)) 48 | out.write(bytes) 49 | out.closeEntry() 50 | } 51 | out.finish() 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/task/JvmdgStubCheckTask.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.task 2 | 3 | import dev.nolij.zumegradle.task.ConstantPool.Companion.toConstantPool 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.file.DirectoryProperty 6 | import org.gradle.api.file.FileCollection 7 | import org.gradle.api.provider.Property 8 | import org.gradle.api.provider.SetProperty 9 | import org.gradle.api.tasks.CacheableTask 10 | import org.gradle.api.tasks.Classpath 11 | import org.gradle.api.tasks.Input 12 | import org.gradle.api.tasks.InputFiles 13 | import org.gradle.api.tasks.TaskAction 14 | import java.io.DataInputStream 15 | import java.io.File 16 | 17 | @CacheableTask 18 | abstract class JvmdgStubCheckTask : DefaultTask() { 19 | init { 20 | group = "verification" 21 | description = "Checks if jvmdg stubs are present in any classes" 22 | } 23 | 24 | @get:InputFiles 25 | @get:Classpath 26 | abstract val classes: Property 27 | 28 | /** 29 | * Any stubs that are allowed to be present in the classes 30 | */ 31 | @get:Input 32 | abstract val allowedStubs: SetProperty 33 | 34 | fun classesRoot(dir: DirectoryProperty) { 35 | classes.set(dir.asFileTree.matching { include("**/*.class") }) 36 | } 37 | 38 | @TaskAction 39 | fun checkStubs() { 40 | val allowedStubs = allowedStubs.get() 41 | val classes = classes.get().filter { it.isFile } 42 | 43 | val allCPEntries = classes.map { getClassInfos(it) } 44 | allCPEntries.forEach { 45 | println("class: ${it.first}") 46 | it.second.forEach { println(" $it") } 47 | } 48 | 49 | val stubs = allCPEntries 50 | .map { (className, stubs) -> 51 | className to stubs.filter { it.startsWith("xyz/wagyourtail/jvmdg/j") && it !in allowedStubs } 52 | } 53 | .filter { (_, stubs) -> stubs.isNotEmpty() } 54 | 55 | if (stubs.isNotEmpty()) { 56 | val message = buildString { 57 | append("Found JVMDowngrader stubs in classes:\n") 58 | for ((className, stubs) in stubs) { 59 | append(" $className:\n") 60 | for (stub in stubs) { 61 | append(" $stub\n") 62 | } 63 | } 64 | } 65 | throw IllegalArgumentException(message) 66 | } 67 | } 68 | 69 | private fun getClassInfos(file: File): Pair> { 70 | val d = DataInputStream(file.inputStream()) 71 | val magic = d.readInt().toUInt() 72 | if (magic != 0xCAFEBABEu) { 73 | throw IllegalArgumentException("Not a class file; magic number is 0x${magic.toString(16)} (file: ${file.name})") 74 | } 75 | 76 | d.readInt() // minor + major version 77 | 78 | val constantPoolCount = d.readUnsignedShort() 79 | val constantPool = buildList { 80 | while (size + 1 < constantPoolCount) { 81 | val entry = ConstantPool.readEntry(d) 82 | if(entry is Invalid) { 83 | error("Unknown constant pool tag ${entry.tag} at index ${size + 1}") 84 | } 85 | 86 | add(entry) 87 | if (entry is LongInfo || entry is DoubleInfo) { 88 | // these take up 2 slots for some reason 89 | add(Invalid(-1)) 90 | } 91 | } 92 | }.toConstantPool() 93 | 94 | d.readUnsignedShort() // access flags 95 | val thisClass = d.readUnsignedShort() 96 | val className = constantPool.get(constantPool.get(thisClass).nameIndex).value 97 | 98 | val stubs = constantPool.entries 99 | .filterIsInstance() 100 | .map { constantPool.get(it.nameIndex).value } 101 | .toSet() 102 | 103 | return className to stubs 104 | } 105 | } 106 | 107 | class ConstantPool(val entries: Array) { 108 | inline operator fun get(index: Int): T { 109 | val entry = entries[index - 1] 110 | if (entry !is T) { 111 | throw IllegalArgumentException("Expected ${T::class.simpleName}, got ${entry::class.simpleName}") 112 | } 113 | return entry 114 | } 115 | 116 | companion object { 117 | fun readEntry(d: DataInputStream): ConstantPoolEntry { 118 | val tag = d.readUnsignedByte() 119 | return when (tag) { 120 | 7 -> ClassInfo(d.readUnsignedShort()) 121 | 9 -> FieldRef(d.readUnsignedShort(), d.readUnsignedShort()) 122 | 10 -> MethodRef(d.readUnsignedShort(), d.readUnsignedShort()) 123 | 11 -> InterfaceMethodRef(d.readUnsignedShort(), d.readUnsignedShort()) 124 | 8 -> StringInfo(d.readUnsignedShort()) 125 | 3 -> IntegerInfo(d.readInt()) 126 | 4 -> FloatInfo(d.readFloat()) 127 | 5 -> LongInfo(d.readLong()) 128 | 6 -> DoubleInfo(d.readDouble()) 129 | 12 -> NameAndType(d.readUnsignedShort(), d.readUnsignedShort()) 130 | 1 -> Utf8Info(d.readUTF()) 131 | 15 -> MethodHandle(d.readUnsignedByte(), d.readUnsignedShort()) 132 | 16 -> MethodType(d.readUnsignedShort()) 133 | 18 -> InvokeDynamic(d.readUnsignedShort(), d.readUnsignedShort()) 134 | else -> Invalid(tag) 135 | } 136 | } 137 | 138 | fun Collection.toConstantPool() = ConstantPool(this.toTypedArray()) 139 | } 140 | } 141 | 142 | sealed interface ConstantPoolEntry 143 | data class ClassInfo(val nameIndex: Int) : ConstantPoolEntry 144 | data class FieldRef(val classIndex: Int, val nameAndTypeIndex: Int) : ConstantPoolEntry 145 | data class MethodRef(val classIndex: Int, val nameAndTypeIndex: Int) : ConstantPoolEntry 146 | data class InterfaceMethodRef(val classIndex: Int, val nameAndTypeIndex: Int) : ConstantPoolEntry 147 | data class StringInfo(val stringIndex: Int) : ConstantPoolEntry 148 | data class IntegerInfo(val value: Int) : ConstantPoolEntry 149 | data class FloatInfo(val value: Float) : ConstantPoolEntry 150 | data class LongInfo(val value: Long) : ConstantPoolEntry 151 | data class DoubleInfo(val value: Double) : ConstantPoolEntry 152 | data class NameAndType(val nameIndex: Int, val descriptorIndex: Int) : ConstantPoolEntry 153 | data class Utf8Info(val value: String) : ConstantPoolEntry 154 | data class MethodHandle(val referenceKind: Int, val referenceIndex: Int) : ConstantPoolEntry 155 | data class MethodType(val descriptorIndex: Int) : ConstantPoolEntry 156 | data class InvokeDynamic(val bootstrapMethodAttrIndex: Int, val nameAndTypeIndex: Int) : ConstantPoolEntry 157 | data class Invalid(val tag: Int) : ConstantPoolEntry -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/task/ProcessJarTask.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.task 2 | 3 | import org.gradle.api.file.RegularFileProperty 4 | import org.gradle.api.provider.Property 5 | import org.gradle.api.tasks.Input 6 | import org.gradle.api.tasks.InputFile 7 | import org.gradle.api.tasks.PathSensitive 8 | import org.gradle.api.tasks.PathSensitivity 9 | import org.gradle.jvm.tasks.Jar 10 | 11 | @Suppress("LeakingThis") 12 | abstract class ProcessJarTask : Jar() { 13 | @get:InputFile 14 | @get:PathSensitive(PathSensitivity.RELATIVE) 15 | abstract val inputJar: RegularFileProperty 16 | 17 | @get:Input 18 | abstract val ignoreSameInputOutput: Property 19 | 20 | init { 21 | ignoreSameInputOutput.convention(false) 22 | 23 | group = "processing" 24 | } 25 | 26 | override fun copy() { 27 | if(!ignoreSameInputOutput.get() && inputJar.get().asFile.equals(archiveFile.get().asFile)) { 28 | throw IllegalStateException("Input jar and output jar are the same file; this breaks caching" + 29 | "\nTo ignore this, set ignoreSameInputOutput to true") 30 | } 31 | process() 32 | } 33 | 34 | protected abstract fun process() 35 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/task/ProguardTask.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.task 2 | 3 | import org.gradle.api.file.RegularFileProperty 4 | import org.gradle.api.provider.ListProperty 5 | import org.gradle.api.provider.Property 6 | import org.gradle.api.tasks.* 7 | import org.gradle.api.tasks.Optional 8 | import proguard.Configuration 9 | import proguard.ConfigurationParser 10 | import proguard.ProGuard 11 | import java.io.File 12 | import java.util.* 13 | 14 | private val javaHome = System.getProperty("java.home")!! 15 | 16 | abstract class ProguardTask : ProcessJarTask() { 17 | 18 | @get:InputFiles 19 | @get:Classpath 20 | abstract val classpath: ListProperty 21 | 22 | @get:InputFiles 23 | abstract val configs: ListProperty 24 | 25 | @get:Input 26 | abstract val options: ListProperty 27 | 28 | @get:Input 29 | abstract val run: Property 30 | 31 | @get:OutputFile 32 | @get:Optional 33 | abstract val mappingsFile: RegularFileProperty 34 | 35 | fun config(config: File) { 36 | configs.add(config) 37 | } 38 | 39 | fun jmod(jmod: String) { 40 | classpath.add(File("$javaHome/jmods/$jmod.jmod")) 41 | } 42 | 43 | override fun process() { 44 | if(!run.get()) { 45 | inputJar.get().asFile.copyTo(archiveFile.get().asFile, true) 46 | return 47 | } 48 | 49 | val cmd = this.options.get().toMutableSet() 50 | 51 | cmd.addAll(configs.get().map { "@${it.absolutePath}" }) 52 | 53 | cmd.addAll(arrayOf( 54 | "-injars", inputJar.get().asFile.absolutePath, 55 | "-outjars", archiveFile.get().asFile.absolutePath 56 | )) 57 | 58 | if (mappingsFile.isPresent) { 59 | cmd.add("-printmapping") 60 | cmd.add(mappingsFile.get().asFile.absolutePath) 61 | } 62 | 63 | cmd.addAll(arrayOf( 64 | "-libraryjars", classpath.get() 65 | .toSet() 66 | .sortedBy { it.name } 67 | .joinToString(File.pathSeparator) { "\"${it.absolutePath}\"" } 68 | )) 69 | 70 | val debug = Properties().apply { 71 | val gradleproperties = project.rootDir.resolve("gradle.properties") 72 | if (gradleproperties.exists()) { 73 | load(gradleproperties.inputStream()) 74 | } 75 | }.getProperty("zumegradle.proguard.keepAttrs")?.toBoolean() ?: false 76 | 77 | if (debug) { 78 | cmd.add("-keepattributes") 79 | cmd.add("*Annotation*,SourceFile,MethodParameters,L*Table") 80 | cmd.add("-dontobfuscate") 81 | } 82 | 83 | project.logger.debug("Proguard command: {}", cmd) 84 | 85 | val configuration = Configuration() 86 | ConfigurationParser(cmd.toTypedArray(), System.getProperties()) 87 | .parse(configuration) 88 | 89 | try { 90 | ProGuard(configuration).execute() 91 | } catch (ex: Exception) { 92 | throw IllegalStateException("ProGuard failed for task ${this.name}", ex) 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/task/SmokeTestTask.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.task 2 | 3 | import dev.nolij.zumegradle.smoketest.SmokeTest 4 | import dev.nolij.zumegradle.smoketest.SmokeTest.Config 5 | import org.gradle.api.DefaultTask 6 | import org.gradle.api.provider.ListProperty 7 | import org.gradle.api.provider.Property 8 | import org.gradle.api.tasks.Input 9 | import org.gradle.jvm.tasks.Jar 10 | import org.gradle.api.tasks.TaskAction 11 | 12 | abstract class SmokeTestTask : DefaultTask() { 13 | init { 14 | group = "verification" 15 | } 16 | 17 | /** 18 | * The task that provides the input jar file. 19 | */ 20 | @get:Input 21 | abstract val inputTask: Property 22 | 23 | @get:Input 24 | abstract val mainDir: Property 25 | 26 | @get:Input 27 | abstract val workDir: Property 28 | 29 | @get:Input 30 | abstract val maxThreads: Property 31 | 32 | @get:Input 33 | abstract val threadTimeout: Property 34 | 35 | @get:Input 36 | abstract val configs: ListProperty 37 | 38 | @get:Input 39 | abstract val portableMCBinary: Property 40 | 41 | fun config(config: Config) { 42 | configs.add(config) 43 | } 44 | 45 | fun configs(vararg configs: Config) { 46 | this.configs.addAll(configs.asList()) 47 | } 48 | 49 | @TaskAction 50 | fun runSmokeTest() { 51 | if (!Zume.auditAndExitEnabled) 52 | error("Smoke testing requires `auditAndExit` support!") 53 | 54 | SmokeTest( 55 | project, 56 | portableMCBinary.get(), 57 | inputTask.get().archiveFile.get().asFile, 58 | mainDir.get(), 59 | workDir.get(), 60 | maxThreads.get(), 61 | threadTimeout.get(), 62 | configs.get() 63 | ).test() 64 | } 65 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/util/AsmHelper.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.util 2 | 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.ClassWriter 5 | import org.objectweb.asm.tree.ClassNode 6 | 7 | fun ClassNode.toBytes(flags: Int = 0): ByteArray { 8 | val writer = ClassWriter(flags) 9 | this.accept(writer) 10 | return writer.toByteArray() 11 | } 12 | 13 | fun ClassNode(bytes: ByteArray): ClassNode { 14 | val node = ClassNode() 15 | ClassReader(bytes).accept(node, 0) 16 | return node 17 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/dev/nolij/zumegradle/util/MappingsHelper.kt: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.util 2 | 3 | import net.fabricmc.mappingio.MappingReader 4 | import net.fabricmc.mappingio.format.MappingFormat 5 | import net.fabricmc.mappingio.tree.MappingTree.ClassMapping 6 | import net.fabricmc.mappingio.tree.MemoryMappingTree 7 | import java.io.File 8 | 9 | fun mappings(file: File, format: MappingFormat = MappingFormat.PROGUARD): MemoryMappingTree { 10 | if (!file.exists()) { 11 | error("Mappings file $file does not exist") 12 | } 13 | 14 | val mappingTree = MemoryMappingTree() 15 | MappingReader.read(file.toPath(), format, mappingTree) 16 | return mappingTree 17 | } 18 | 19 | @Suppress("INACCESSIBLE_TYPE", "NAME_SHADOWING") 20 | fun MemoryMappingTree.map(src: String): String { 21 | val src = src.replace('.', '/') 22 | val dstNamespaceIndex = getNamespaceId(dstNamespaces[0]) 23 | val classMapping: ClassMapping? = getClass(src) 24 | if (classMapping == null) { 25 | println("Class $src not found in mappings") 26 | return src 27 | } 28 | return classMapping.getDstName(dstNamespaceIndex).replace('/', '.') 29 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle 2 | org.gradle.jvmargs = -Xmx4G -XX:+EnableDynamicAgentLoading 3 | org.gradle.daemon = true 4 | org.gradle.parallel = true 5 | org.gradle.caching = true 6 | org.gradle.configuration-cache = false 7 | 8 | # Mod Properties 9 | maven_group = dev.nolij 10 | mod_id = zume 11 | mod_version = 2.0 12 | mod_name = Zume 13 | mod_description = An over-engineered Zoom mod by Nolij 14 | nolij = Nolij (@xdMatthewbx#1337) 15 | mod_url = https://modrinth.com/mod/zume 16 | repo_url = https://github.com/Nolij/Zume 17 | issue_url = https://github.com/Nolij/Zume/issues 18 | 19 | # Fabric 20 | # https://modmuss50.me/fabric.html 21 | fabric_version = 0.16.13 22 | minimum_fabric_version = 0.15.0 23 | 24 | ## Modern Fabric 25 | # https://modmuss50.me/fabric.html 26 | modern_minecraft_version = 1.20.1 27 | modern_fabric_api_version = 0.92.5+1.20.1 28 | 29 | ## Legacy Fabric 30 | # https://grayray75.github.io/LegacyFabric-Versions/?version=1.7.10 31 | legacy_minecraft_version = 1.7.10 32 | legacy_mappings_version = 568 33 | legacy_fabric_api_version = 1.11.1+1.7.10 34 | 35 | ## Primitive Fabric 36 | # https://babric.github.io/develop/ 37 | primitive_minecraft_version = b1.7.3 38 | # https://maven.wispforest.io/#/releases/me/alphamode/nostalgia/ 39 | primitive_mappings_version = b1.7.3+build.35 40 | babric_version = 0.16.13 41 | 42 | # https://maven.glass-launcher.net/#/releases/net/modificationstation/StationAPI 43 | # https://maven.glass-launcher.net/#/snapshots/net/modificationstation/StationAPI 44 | station_api_version = 259f128 45 | 46 | # Forge 47 | forge_minecraft_range = [1.14,1.20.5) 48 | 49 | ## NeoForge 50 | # https://projects.neoforged.net/neoforged/neoforge 51 | neoforge_minecraft_version = 1.21.1 52 | neoforge_minecraft_range = [1.20.4,) 53 | neoforge_version = 145 54 | neoforge_neoforge_range = [20.4.195,) 55 | neoforge_parchment_version = 2024.11.17 56 | 57 | ## LexForge 58 | # https://files.minecraftforge.net/net/minecraftforge/forge/index_1.20.1.html 59 | lexforge_minecraft_version = 1.20.1 60 | lexforge_minecraft_range = [1.19,1.20.6) 61 | lexforge_version = 47.4.0 62 | lexforge_parchment_version = 2023.09.03 63 | 64 | ## LexForge 18 65 | # https://files.minecraftforge.net/net/minecraftforge/forge/index_1.18.2.html 66 | lexforge18_minecraft_version = 1.18.2 67 | lexforge18_minecraft_range = [1.17,1.19) 68 | lexforge18_version = 40.3.10 69 | lexforge18_parchment_version = 2022.11.06 70 | 71 | ## LexForge 16 72 | # https://files.minecraftforge.net/net/minecraftforge/forge/index_1.16.5.html 73 | lexforge16_minecraft_version = 1.16.5 74 | lexforge16_minecraft_range = [1.14,1.17) 75 | lexforge16_version = 36.2.42 76 | lexforge16_parchment_version = 2022.03.06 77 | 78 | # Legacy Forge 79 | 80 | ## Archaic Forge 81 | # https://files.minecraftforge.net/net/minecraftforge/forge/index_1.7.10.html 82 | archaic_minecraft_version = 1.7.10 83 | archaic_minecraft_range = [1.7.10] 84 | archaic_mappings_version = 12-1.7.10 85 | archaic_forge_version = 10.13.4.1614-1.7.10 86 | # https://github.com/LegacyModdingMC/UniMixins/releases/latest 87 | unimixins_version = 0.1.20 88 | 89 | ## Vintage Forge 90 | # https://files.minecraftforge.net/net/minecraftforge/forge/index_1.12.2.html 91 | vintage_minecraft_version = 1.12.2 92 | vintage_minecraft_range = [1.8.9,1.12.2] 93 | vintage_mappings_version = 39-1.12 94 | vintage_forge_version = 14.23.5.2860 95 | # https://github.com/CleanroomMC/MixinBooter/releases/latest 96 | mixinbooter_version = 9.3 97 | 98 | # Dependencies 99 | # https://maven.blamejared.com/org/embeddedt/ 100 | ## https://maven.blamejared.com/org/embeddedt/embeddium-fabric-1.20.1/ 101 | embeddium_fabric_version = 0.3.26-beta.106+mc1.20.1 102 | ## https://maven.blamejared.com/org/embeddedt/embeddium-1.20.1/ 103 | embeddium_lexforge_version = 0.3.31-beta.53+mc1.20.1 104 | ## https://maven.blamejared.com/org/embeddedt/embeddium-1.21/ 105 | embeddium_neoforge_version = 1.0.8-beta.367+mc1.21 106 | # https://github.com/unimined/JvmDowngrader/releases/latest 107 | jvmdg_version = 1.3.0-20250403.003749-22 108 | # https://github.com/Nolij/ZSON/releases/latest 109 | zson_version = 0.5.0 110 | # https://github.com/Nolij/LibNolij/releases/latest 111 | libnolij_version = 0.5.0 112 | # https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core 113 | log4j_version = 3.0.0-beta2 114 | # https://central.sonatype.com/artifact/org.slf4j/slf4j-api 115 | slf4j_version = 2.1.0-alpha1 116 | # https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-slf4j18-impl 117 | log4j_slf4j_version = 2.18.0 118 | # https://central.sonatype.com/artifact/org.ow2.asm/asm-tree 119 | asm_version = 9.7.1 120 | # https://github.com/SpongePowered/Mixin/releases/latest 121 | mixin_version = 0.8.5 122 | # https://central.sonatype.com/artifact/org.jetbrains/annotations 123 | jetbrains_annotations_version = 26.0.1 124 | # https://github.com/manifold-systems/manifold 125 | manifold_version = 2025.1.1 126 | # https://pypi.org/project/portablemc/ 127 | portablemc_version = 4.4.1 128 | 129 | # Gradle Plugins 130 | # https://maven.fabricmc.net/net/fabricmc/mapping-io/ 131 | mapping_io_version = 0.3.0 132 | # https://github.com/johnrengelman/shadow/blob/main/gradle/dependencies.gradle 133 | shadow_ant_version = 1.10.4 134 | # https://plugins.gradle.org/plugin/com.gradleup.shadow 135 | shadow_version = 8.3.5 136 | # https://central.sonatype.com/artifact/com.guardsquare/proguard-base 137 | proguard_version = 7.7.0 138 | # https://github.com/unimined/unimined/releases/latest 139 | unimined_version = 1.3.13 140 | # https://plugins.gradle.org/plugin/com.github.gmazzo.buildconfig 141 | buildconfig_version = 5.5.0 142 | # https://github.com/ajoberstar/grgit/releases/latest 143 | grgit_version = 5.3.0 144 | # https://plugins.gradle.org/plugin/me.modmuss50.mod-publish-plugin 145 | mod_publish_version = 0.7.4 146 | # https://github.com/xvik/gradle-use-python-plugin/releases/latest 147 | use_python_version = 4.1.0 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/Zume/1b8db932dd5fa62dd89a096638bd0f78baa8e6f0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /icon_padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/Zume/1b8db932dd5fa62dd89a096638bd0f78baa8e6f0/icon_padded.png -------------------------------------------------------------------------------- /icon_padded_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/Zume/1b8db932dd5fa62dd89a096638bd0f78baa8e6f0/icon_padded_large.png -------------------------------------------------------------------------------- /integration/build.gradle.kts: -------------------------------------------------------------------------------- 1 | tasks.jar { enabled = false } -------------------------------------------------------------------------------- /integration/embeddium/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("xyz.wagyourtail.unimined") 3 | } 4 | 5 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 6 | 7 | unimined.minecraft { 8 | combineWith(project(":api").sourceSets.main.get()) 9 | 10 | version("modern_minecraft_version"()) 11 | 12 | runs.off = true 13 | 14 | fabric { 15 | loader("fabric_version"()) 16 | } 17 | 18 | mappings { 19 | mojmap() 20 | } 21 | 22 | defaultRemapJar = false 23 | } 24 | 25 | repositories { 26 | maven("https://maven.blamejared.com") 27 | } 28 | 29 | dependencies { 30 | "modImplementation"("org.embeddedt:embeddium-fabric-1.20.1:${"embeddium_fabric_version"()}") 31 | } -------------------------------------------------------------------------------- /integration/embeddium/src/main/java/dev/nolij/zume/integration/embeddium/ZumeOptionsStorage.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.integration.embeddium; 2 | 3 | import dev.nolij.zume.api.config.v1.ZumeConfig; 4 | import dev.nolij.zume.api.config.v1.ZumeConfigAPI; 5 | import dev.nolij.zume.impl.ZumeConstants; 6 | import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage; 7 | import org.embeddedt.embeddium.client.gui.options.OptionIdentifier; 8 | 9 | public final class ZumeOptionsStorage implements OptionStorage { 10 | 11 | //region Pages 12 | public static final OptionIdentifier GENERAL = 13 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general"); 14 | public static final OptionIdentifier ADVANCED = 15 | OptionIdentifier.create(ZumeConstants.MOD_ID, "advanced"); 16 | //endregion 17 | 18 | //region Groups 19 | public static final OptionIdentifier BEHAVIOUR = 20 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general/behaviour"); 21 | public static final OptionIdentifier ANIMATIONS = 22 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general/animations"); 23 | public static final OptionIdentifier THIRD_PERSON = 24 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general/third_person"); 25 | 26 | public static final OptionIdentifier EXPONENTS = 27 | OptionIdentifier.create(ZumeConstants.MOD_ID, "advanced/exponents"); 28 | public static final OptionIdentifier MISC = 29 | OptionIdentifier.create(ZumeConstants.MOD_ID, "advanced/misc"); 30 | //endregion 31 | 32 | //region Options 33 | public static final OptionIdentifier ENABLE_CINEMATIC_ZOOM = 34 | OptionIdentifier.create(ZumeConstants.MOD_ID, "enable_cinematic_zoom", boolean.class); 35 | public static final OptionIdentifier MOUSE_SENSITIVITY_FLOOR = 36 | OptionIdentifier.create(ZumeConstants.MOD_ID, "mouse_sensitivity_floor", int.class); 37 | public static final OptionIdentifier ZOOM_SPEED = 38 | OptionIdentifier.create(ZumeConstants.MOD_ID, "zoom_speed", int.class); 39 | public static final OptionIdentifier ENABLE_ZOOM_SCROLLING = 40 | OptionIdentifier.create(ZumeConstants.MOD_ID, "enable_zoom_scrolling", boolean.class); 41 | public static final OptionIdentifier ZOOM_SMOOTHNESS_MS = 42 | OptionIdentifier.create(ZumeConstants.MOD_ID, "zoom_smoothness_ms", int.class); 43 | public static final OptionIdentifier ANIMATION_EASING_EXPONENT = 44 | OptionIdentifier.create(ZumeConstants.MOD_ID, "animation_easing_exponent", int.class); 45 | public static final OptionIdentifier ZOOM_EASING_EXPONENT = 46 | OptionIdentifier.create(ZumeConstants.MOD_ID, "zoom_easing_exponent", int.class); 47 | public static final OptionIdentifier DEFAULT_ZOOM = 48 | OptionIdentifier.create(ZumeConstants.MOD_ID, "default_zoom", int.class); 49 | public static final OptionIdentifier FIRST_PERSON_TOGGLE_MODE = 50 | OptionIdentifier.create(ZumeConstants.MOD_ID, "first_person_toggle_mode", boolean.class); 51 | public static final OptionIdentifier THIRD_PERSON_TOGGLE_MODE = 52 | OptionIdentifier.create(ZumeConstants.MOD_ID, "third_person_toggle_mode", boolean.class); 53 | public static final OptionIdentifier MIN_FOV = 54 | OptionIdentifier.create(ZumeConstants.MOD_ID, "min_fov", int.class); 55 | public static final OptionIdentifier MAX_THIRD_PERSON_ZOOM_BLOCKS = 56 | OptionIdentifier.create(ZumeConstants.MOD_ID, "max_third_person_zoom_blocks", int.class); 57 | public static final OptionIdentifier MIN_THIRD_PERSON_ZOOM_BLOCKS = 58 | OptionIdentifier.create(ZumeConstants.MOD_ID, "min_third_person_zoom_blocks", int.class); 59 | public static final OptionIdentifier DISABLE = 60 | OptionIdentifier.create(ZumeConstants.MOD_ID, "disable", boolean.class); 61 | //endregion 62 | 63 | private final ZumeConfig storage = ZumeConfigAPI.getSnapshot(); 64 | 65 | @Override 66 | public ZumeConfig getData() { 67 | return storage; 68 | } 69 | 70 | @Override 71 | public void save() { 72 | ZumeConfigAPI.replaceConfig(storage); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /integration/embeddium/src/main/java/dev/nolij/zume/integration/implementation/embeddium/ZumeEmbeddiumConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.integration.implementation.embeddium; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import dev.nolij.zume.integration.embeddium.ZumeOptionsStorage; 5 | import me.jellysquid.mods.sodium.client.gui.options.Option; 6 | import me.jellysquid.mods.sodium.client.gui.options.OptionFlag; 7 | import me.jellysquid.mods.sodium.client.gui.options.OptionGroup; 8 | import me.jellysquid.mods.sodium.client.gui.options.OptionImpl; 9 | import me.jellysquid.mods.sodium.client.gui.options.OptionPage; 10 | import me.jellysquid.mods.sodium.client.gui.options.control.Control; 11 | import me.jellysquid.mods.sodium.client.gui.options.control.ControlValueFormatter; 12 | import me.jellysquid.mods.sodium.client.gui.options.control.SliderControl; 13 | import me.jellysquid.mods.sodium.client.gui.options.control.TickBoxControl; 14 | import net.minecraft.network.chat.Component; 15 | import org.embeddedt.embeddium.api.OptionGUIConstructionEvent; 16 | import org.embeddedt.embeddium.api.eventbus.EventHandlerRegistrar; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import static dev.nolij.zume.integration.embeddium.ZumeOptionsStorage.*; 22 | 23 | public class ZumeEmbeddiumConfigScreen implements EventHandlerRegistrar.Handler { 24 | 25 | private Control percentageControl(final Option option) { 26 | return new SliderControl(option, 0, 100, 1, ControlValueFormatter.percentage()); 27 | } 28 | 29 | private Control exponentControl(final Option option) { 30 | return new SliderControl(option, 100, 500, 25, v -> switch (v) { 31 | case 100 -> Component.translatable("zume.linear"); 32 | case 200 -> Component.translatable("zume.quad"); 33 | case 300 -> Component.translatable("zume.cubic"); 34 | case 400 -> Component.translatable("zume.quart"); 35 | case 500 -> Component.translatable("zume.quint"); 36 | default -> Component.literal("x^" + (v / 100D)); 37 | }); 38 | } 39 | 40 | public ZumeEmbeddiumConfigScreen() { 41 | OptionGUIConstructionEvent.BUS.addListener(this); 42 | } 43 | 44 | @Override 45 | public void acceptEvent(OptionGUIConstructionEvent event) { 46 | final ZumeOptionsStorage storage = new ZumeOptionsStorage(); 47 | 48 | final List generalGroups = new ArrayList<>(); 49 | final List advancedGroups = new ArrayList<>(); 50 | 51 | generalGroups.add(OptionGroup.createBuilder() 52 | .setId(BEHAVIOUR) 53 | .add(OptionImpl.createBuilder(int.class, storage) 54 | .setId(ZOOM_SPEED) 55 | .setControl(option -> new SliderControl(option, 5, 50, 5, ControlValueFormatter.number())) 56 | .setBinding( 57 | (config, value) -> config.zoomSpeed = value.shortValue(), 58 | config -> (int) config.zoomSpeed) 59 | .build()) 60 | .add(OptionImpl.createBuilder(boolean.class, storage) 61 | .setId(ENABLE_ZOOM_SCROLLING) 62 | .setControl(TickBoxControl::new) 63 | .setBinding( 64 | (config, value) -> config.isZoomScrollingEnabled = value, 65 | config -> config.isZoomScrollingEnabled) 66 | .build()) 67 | .add(OptionImpl.createBuilder(boolean.class, storage) 68 | .setId(ENABLE_CINEMATIC_ZOOM) 69 | .setControl(TickBoxControl::new) 70 | .setBinding( 71 | (config, value) -> config.isCinematicZoomEnabled = value, 72 | config -> config.isCinematicZoomEnabled) 73 | .build()) 74 | .add(OptionImpl.createBuilder(int.class, storage) 75 | .setId(MOUSE_SENSITIVITY_FLOOR) 76 | .setControl(this::percentageControl) 77 | .setBinding( 78 | (config, value) -> config.mouseSensitivityFloor = Math.max(value * 0.01D, 0.01D), 79 | config -> (int) Math.round(config.mouseSensitivityFloor * 100D)) 80 | .build()) 81 | .add(OptionImpl.createBuilder(int.class, storage) 82 | .setId(DEFAULT_ZOOM) 83 | .setControl(this::percentageControl) 84 | .setBinding( 85 | (config, value) -> config.defaultZoom = value * 0.01D, 86 | config -> (int) Math.round(config.defaultZoom * 100D)) 87 | .build()) 88 | .add(OptionImpl.createBuilder(boolean.class, storage) 89 | .setId(FIRST_PERSON_TOGGLE_MODE) 90 | .setControl(TickBoxControl::new) 91 | .setBinding( 92 | (config, value) -> config.isFirstPersonToggleModeEnabled = value, 93 | config -> config.isFirstPersonToggleModeEnabled) 94 | .build()) 95 | .add(OptionImpl.createBuilder(boolean.class, storage) 96 | .setId(THIRD_PERSON_TOGGLE_MODE) 97 | .setControl(TickBoxControl::new) 98 | .setBinding( 99 | (config, value) -> config.isThirdPersonToggleModeEnabled = value, 100 | config -> config.isThirdPersonToggleModeEnabled) 101 | .build()) 102 | .build()); 103 | generalGroups.add(OptionGroup.createBuilder() 104 | .setId(ANIMATIONS) 105 | .add(OptionImpl.createBuilder(int.class, storage) 106 | .setId(ZOOM_SMOOTHNESS_MS) 107 | .setControl(option -> 108 | new SliderControl(option, 0, 500, 25, 109 | v -> v > 0 110 | ? Component.literal(v + "ms") 111 | : Component.translatable("zume.instant"))) 112 | .setBinding( 113 | (config, value) -> config.zoomSmoothnessMilliseconds = value.shortValue(), 114 | config -> (int) config.zoomSmoothnessMilliseconds) 115 | .build()) 116 | .build()); 117 | generalGroups.add(OptionGroup.createBuilder() 118 | .setId(THIRD_PERSON) 119 | .add(OptionImpl.createBuilder(int.class, storage) 120 | .setId(MAX_THIRD_PERSON_ZOOM_BLOCKS) 121 | .setControl(option -> 122 | new SliderControl(option, 0, 30, 1, 123 | v -> v > 0 124 | ? Component.translatable("zume.blocks", v) 125 | : Component.translatable("zume.disabled"))) 126 | .setBinding( 127 | (config, value) -> config.maximumThirdPersonZoomBlocks = value, 128 | config -> (int) config.maximumThirdPersonZoomBlocks) 129 | .build()) 130 | .add(OptionImpl.createBuilder(int.class, storage) 131 | .setId(MIN_THIRD_PERSON_ZOOM_BLOCKS) 132 | .setControl(option -> 133 | new SliderControl(option, 0, 10, 1, 134 | v -> Component.translatable("zume.blocks", v > 0 ? v : 0.5))) 135 | .setBinding( 136 | (config, value) -> config.minimumThirdPersonZoomBlocks = value > 0 ? value : 0.5, 137 | config -> (int) config.minimumThirdPersonZoomBlocks) 138 | .build()) 139 | .build()); 140 | 141 | advancedGroups.add(OptionGroup.createBuilder() 142 | .setId(EXPONENTS) 143 | .add(OptionImpl.createBuilder(int.class, storage) 144 | .setId(ANIMATION_EASING_EXPONENT) 145 | .setControl(this::exponentControl) 146 | .setBinding( 147 | (config, value) -> config.animationEasingExponent = value / 100D, 148 | config -> (int) Math.round(config.animationEasingExponent * 100)) 149 | .build()) 150 | .add(OptionImpl.createBuilder(int.class, storage) 151 | .setId(ZOOM_EASING_EXPONENT) 152 | .setControl(this::exponentControl) 153 | .setBinding( 154 | (config, value) -> config.zoomEasingExponent = value / 100D, 155 | config -> (int) Math.round(config.zoomEasingExponent * 100)) 156 | .build()) 157 | .build()); 158 | advancedGroups.add(OptionGroup.createBuilder() 159 | .setId(MISC) 160 | .add(OptionImpl.createBuilder(int.class, storage) 161 | .setId(MIN_FOV) 162 | .setControl(option -> 163 | new SliderControl(option, -2, 1, 1, 164 | v -> Component.literal(String.valueOf(Math.pow(10, v))))) 165 | .setBinding( 166 | (config, value) -> config.minimumFOV = Math.pow(10, value), 167 | config -> (int) Math.log10(config.minimumFOV)) 168 | .build()) 169 | .add(OptionImpl.createBuilder(boolean.class, storage) 170 | .setId(DISABLE) 171 | .setControl(TickBoxControl::new) 172 | .setBinding( 173 | (config, value) -> config.isDisabled = value, 174 | config -> config.isDisabled) 175 | .setFlags(OptionFlag.REQUIRES_GAME_RESTART) 176 | .build()) 177 | .build()); 178 | 179 | event.getPages().add(new OptionPage( 180 | GENERAL, 181 | Component.translatable("zume.options.pages.general"), 182 | ImmutableList.copyOf(generalGroups))); 183 | event.getPages().add(new OptionPage( 184 | ADVANCED, 185 | Component.translatable("zume.options.pages.advanced"), 186 | ImmutableList.copyOf(advancedGroups))); 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /legacy/build.gradle.kts: -------------------------------------------------------------------------------- 1 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 2 | 3 | unimined.minecraft { 4 | version("legacy_minecraft_version"()) 5 | 6 | fabric { 7 | loader("fabric_version"()) 8 | } 9 | 10 | mappings { 11 | legacyIntermediary() 12 | legacyYarn("legacy_mappings_version"()) 13 | } 14 | } 15 | 16 | dependencies { 17 | compileOnly(project(":stubs")) 18 | 19 | "modImplementation"(fabricApi.legacyFabricModule("legacy-fabric-keybindings-api-v1-common", "legacy_fabric_api_version"())) 20 | } -------------------------------------------------------------------------------- /legacy/src/main/java/dev/nolij/zume/legacy/LegacyZume.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.legacy; 2 | 3 | import dev.nolij.libnolij.refraction.Refraction; 4 | import dev.nolij.zume.impl.CameraPerspective; 5 | import dev.nolij.zume.impl.IZumeImplementation; 6 | import dev.nolij.zume.impl.Zume; 7 | import dev.nolij.zume.mixin.legacy.GameRendererAccessor; 8 | import net.fabricmc.api.ClientModInitializer; 9 | import net.fabricmc.api.EnvType; 10 | import net.fabricmc.loader.api.FabricLoader; 11 | import net.legacyfabric.fabric.api.client.keybinding.v1.KeyBindingHelper; 12 | import net.minecraft.client.MinecraftClient; 13 | import net.minecraft.client.option.KeyBinding; 14 | import net.minecraft.client.util.SmoothUtil; 15 | 16 | import java.lang.invoke.MethodHandle; 17 | import java.lang.invoke.MethodType; 18 | 19 | public class LegacyZume implements ClientModInitializer, IZumeImplementation { 20 | 21 | @Override 22 | public void onInitializeClient() { 23 | if (FabricLoader.getInstance().getEnvironmentType() != EnvType.CLIENT) 24 | return; 25 | 26 | Zume.LOGGER.info("Loading Legacy Zume..."); 27 | 28 | Zume.registerImplementation(this, FabricLoader.getInstance().getConfigDir()); 29 | if (Zume.disabled) 30 | return; 31 | 32 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 33 | KeyBindingHelper.registerKeyBinding(keyBind.value); 34 | } 35 | } 36 | 37 | @Override 38 | public boolean isZoomPressed() { 39 | return MinecraftClient.getInstance().currentScreen == null && ZumeKeyBind.ZOOM.isPressed(); 40 | } 41 | 42 | @Override 43 | public boolean isZoomInPressed() { 44 | return ZumeKeyBind.ZOOM_IN.isPressed(); 45 | } 46 | 47 | @Override 48 | public boolean isZoomOutPressed() { 49 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 50 | } 51 | 52 | @Override 53 | public CameraPerspective getCameraPerspective() { 54 | return CameraPerspective.values()[MinecraftClient.getInstance().options.perspective]; 55 | } 56 | 57 | private static final boolean USE_CINEMATIC_CAMERA_WORKAROUND = Refraction.safe() 58 | .getMethodOrNull(SmoothUtil.class, "method_10852") == null; 59 | 60 | @Override 61 | public void onZoomActivate() { 62 | if (USE_CINEMATIC_CAMERA_WORKAROUND && 63 | Zume.config.enableCinematicZoom && !MinecraftClient.getInstance().options.smoothCameraEnabled) { 64 | final GameRendererAccessor gameRenderer = (GameRendererAccessor) MinecraftClient.getInstance().gameRenderer; 65 | gameRenderer.setCursorXSmoother(new SmoothUtil()); 66 | gameRenderer.setCursorYSmoother(new SmoothUtil()); 67 | gameRenderer.setCursorDeltaX(0F); 68 | gameRenderer.setCursorDeltaY(0F); 69 | gameRenderer.setSmoothedCursorDeltaX(0F); 70 | gameRenderer.setSmoothedCursorDeltaY(0F); 71 | gameRenderer.setLastTickDelta(0F); 72 | } 73 | } 74 | 75 | private static final MethodHandle KEYBINDING_INIT_CATEGORY = 76 | Refraction.safe().getConstructorOrNull( 77 | KeyBinding.class, 78 | MethodType.methodType(KeyBinding.class, String.class, int.class, String.class), 79 | String.class, int.class, String.class 80 | ); 81 | private static final MethodHandle KEYBINDING_INIT_NO_CATEGORY = 82 | Refraction.safe().getConstructorOrNull( 83 | KeyBinding.class, 84 | MethodType.methodType(KeyBinding.class, String.class, int.class), 85 | String.class, int.class 86 | ); 87 | 88 | public static KeyBinding newKeyBinding(String translationKey, int keyCode, String category) { 89 | if (KEYBINDING_INIT_CATEGORY != null) 90 | return (KeyBinding) KEYBINDING_INIT_CATEGORY.invokeExact(translationKey, keyCode, category); 91 | else 92 | //noinspection DataFlowIssue 93 | return (KeyBinding) KEYBINDING_INIT_NO_CATEGORY.invokeExact(translationKey, keyCode); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /legacy/src/main/java/dev/nolij/zume/legacy/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.legacy; 2 | 3 | import net.minecraft.client.option.KeyBinding; 4 | import org.lwjgl.input.Keyboard; 5 | 6 | public enum ZumeKeyBind { 7 | 8 | ZOOM("zume.zoom", Keyboard.KEY_Z), 9 | ZOOM_IN("zume.zoom_in", Keyboard.KEY_EQUALS), 10 | ZOOM_OUT("zume.zoom_out", Keyboard.KEY_MINUS), 11 | 12 | ; 13 | 14 | public final KeyBinding value; 15 | 16 | public boolean isPressed() { 17 | return value.isPressed(); 18 | } 19 | 20 | ZumeKeyBind(String translationKey, int code) { 21 | this.value = LegacyZume.newKeyBinding(translationKey, code, "zume"); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /legacy/src/main/java/dev/nolij/zume/mixin/legacy/GameRendererAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.legacy; 2 | 3 | import net.minecraft.client.render.GameRenderer; 4 | import net.minecraft.client.util.SmoothUtil; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(GameRenderer.class) 9 | public interface GameRendererAccessor { 10 | 11 | @Accessor("cursorXSmoother") 12 | void setCursorXSmoother(SmoothUtil value); 13 | @Accessor("cursorYSmoother") 14 | void setCursorYSmoother(SmoothUtil value); 15 | @Accessor("cursorDeltaX") 16 | void setCursorDeltaX(float value); 17 | @Accessor("cursorDeltaY") 18 | void setCursorDeltaY(float value); 19 | @Accessor("smoothedCursorDeltaX") 20 | void setSmoothedCursorDeltaX(float value); 21 | @Accessor("smoothedCursorDeltaY") 22 | void setSmoothedCursorDeltaY(float value); 23 | @Accessor("lastTickDelta") 24 | void setLastTickDelta(float value); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /legacy/src/main/java/dev/nolij/zume/mixin/legacy/GameRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.legacy; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 5 | import dev.nolij.zume.impl.Zume; 6 | import net.minecraft.client.render.GameRenderer; 7 | import org.spongepowered.asm.mixin.Dynamic; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(GameRenderer.class) 15 | public abstract class GameRendererMixin { 16 | 17 | @Dynamic 18 | @Inject(method = { 19 | "method_1331", // archaic 20 | "method_9775(FJ)V" // vintage 21 | }, at = @At("HEAD")) 22 | public void zume$render$HEAD(CallbackInfo ci) { 23 | Zume.renderHook(); 24 | } 25 | 26 | @ModifyReturnValue(method = "getFov", at = @At("TAIL")) 27 | public float zume$getFOV$TAIL(float original) { 28 | if (Zume.isFOVHookActive()) 29 | return (float) Zume.fovHook(original); 30 | 31 | return original; 32 | } 33 | 34 | @Dynamic 35 | @ModifyExpressionValue(method = { 36 | "method_1331", "tick", // archaic 37 | "method_9775(FJ)V" // vintage 38 | }, at = @At(value = "FIELD", target = "Lnet/minecraft/client/option/GameOptions;smoothCameraEnabled:Z")) 39 | public boolean zume$smoothCameraEnabled(boolean original) { 40 | return Zume.cinematicCameraEnabledHook(original); 41 | } 42 | 43 | @Dynamic 44 | @ModifyExpressionValue(method = { 45 | "method_1331", "tick", // archaic 46 | "method_9775(FJ)V" // vintage 47 | }, at = @At(value = "FIELD", target = "Lnet/minecraft/client/option/GameOptions;sensitivity:F")) 48 | public float zume$mouseSensitivity(float original) { 49 | return (float) Zume.mouseSensitivityHook(original); 50 | } 51 | 52 | @ModifyVariable(method = "transformCamera", at = @At(value = "STORE", ordinal = 0), ordinal = 3) 53 | public double zume$transformCamera$thirdPersonDistance(double original) { 54 | return Zume.thirdPersonCameraHook(original); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /legacy/src/main/java/dev/nolij/zume/mixin/legacy/KeyBindingMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.legacy; 2 | 3 | import dev.nolij.zumegradle.proguard.ProGuardKeep; 4 | import net.minecraft.client.option.KeyBinding; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | 8 | @Mixin(KeyBinding.class) 9 | public abstract class KeyBindingMixin { 10 | 11 | @Shadow private boolean pressed; 12 | 13 | // ugly hack for <=6.4 compat 14 | @SuppressWarnings({"MissingUnique", "unused"}) 15 | @ProGuardKeep 16 | public boolean method_6619() { 17 | return this.pressed; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /legacy/src/main/java/dev/nolij/zume/mixin/legacy/MinecraftClientMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.legacy; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import dev.nolij.zume.impl.Zume; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.entity.player.PlayerInventory; 7 | import org.spongepowered.asm.mixin.Dynamic; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | 11 | @Mixin(value = MinecraftClient.class, priority = 500) 12 | public abstract class MinecraftClientMixin { 13 | 14 | @Dynamic 15 | @WrapWithCondition(method = { 16 | "tick", // archaic 17 | "method_12141()V" // vintage 18 | }, at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerInventory;scrollInHotbar(I)V")) 19 | public boolean onMouseScroll$scrollInHotbar(PlayerInventory instance, int scrollAmount) { 20 | return !Zume.mouseScrollHook(scrollAmount); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /legacy/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "zume", 4 | "version": "${mod_version}", 5 | "name": "${mod_name}", 6 | "description": "${mod_description}", 7 | "authors": [ 8 | "${nolij}" 9 | ], 10 | "contact": { 11 | "website": "${mod_url}", 12 | "repo": "${repo_url}", 13 | "issues": "${issue_url}" 14 | }, 15 | "license": "OSL-3.0", 16 | "icon": "icon.png", 17 | "environment": "client", 18 | "entrypoints": { 19 | "client": [ 20 | "dev.nolij.zume.legacy.LegacyZume" 21 | ] 22 | }, 23 | "mixins": [ 24 | "zume-legacy.mixins.json" 25 | ], 26 | "depends": { 27 | "fabricloader": ">=${fabric_version}", 28 | "legacy-fabric-keybinding-api-v1-common": "*" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /legacy/src/main/resources/zume-legacy.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "legacy.MinecraftClientMixin", 8 | "legacy.GameRendererAccessor", 9 | "legacy.GameRendererMixin", 10 | "legacy.KeyBindingMixin" 11 | ], 12 | "injectors": { 13 | "defaultRequire": 1 14 | }, 15 | "refmap": "zume-legacy-refmap.json" 16 | } 17 | -------------------------------------------------------------------------------- /lexforge/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import xyz.wagyourtail.unimined.internal.minecraft.patch.forge.ForgeLikeMinecraftTransformer 2 | 3 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 4 | 5 | val modRuntimeOnly: Configuration by configurations.creating { 6 | configurations.runtimeClasspath.get().extendsFrom(this) 7 | } 8 | 9 | unimined.minecraft { 10 | combineWith(project(":integration:embeddium").sourceSets.main.get()) 11 | 12 | version("lexforge_minecraft_version"()) 13 | 14 | minecraftForge { 15 | loader("lexforge_version"()) 16 | mixinConfig("zume-lexforge.mixins.json") 17 | } 18 | 19 | mappings { 20 | mojmap() 21 | parchment(mcVersion = "lexforge_minecraft_version"(), version = "lexforge_parchment_version"()) 22 | } 23 | 24 | mods { 25 | remap(modRuntimeOnly) { 26 | mixinRemap { 27 | off() 28 | } 29 | } 30 | } 31 | 32 | runs.config("client") { 33 | jvmArguments.addAll( 34 | "-Dmixin.env.remapRefMap=true", 35 | "-Dmixin.env.refMapRemappingFile=${(mcPatcher as ForgeLikeMinecraftTransformer).srgToMCPAsSRG}" 36 | ) 37 | } 38 | } 39 | 40 | repositories { 41 | maven("https://maven.blamejared.com") 42 | } 43 | 44 | dependencies { 45 | compileOnly(project(":stubs")) 46 | 47 | // mixins fail to apply due to Unimined not liking Embeddium's empty mixin list; test in prod 48 | // modRuntimeOnly("org.embeddedt:embeddium-1.20.1:${"embeddium_lexforge_version"()}") 49 | } -------------------------------------------------------------------------------- /lexforge/src/main/java/dev/nolij/zume/lexforge/LexZume.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge; 2 | 3 | import dev.nolij.libnolij.refraction.Refraction; 4 | import dev.nolij.zume.impl.CameraPerspective; 5 | import dev.nolij.zume.impl.IZumeImplementation; 6 | import dev.nolij.zume.impl.Zume; 7 | import dev.nolij.zume.integration.implementation.embeddium.ZumeEmbeddiumConfigScreen; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraftforge.client.event.InputEvent; 10 | import net.minecraftforge.client.event.RegisterKeyMappingsEvent; 11 | import net.minecraftforge.client.event.ViewportEvent; 12 | import net.minecraftforge.common.MinecraftForge; 13 | import net.minecraftforge.event.TickEvent; 14 | import net.minecraftforge.eventbus.api.EventPriority; 15 | import net.minecraftforge.fml.common.Mod; 16 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 17 | import net.minecraftforge.fml.loading.FMLEnvironment; 18 | import net.minecraftforge.fml.loading.FMLPaths; 19 | 20 | import java.lang.invoke.MethodHandle; 21 | 22 | import static dev.nolij.zume.impl.ZumeConstants.MOD_ID; 23 | 24 | @Mod(MOD_ID) 25 | public class LexZume implements IZumeImplementation { 26 | 27 | public LexZume() { 28 | if (!FMLEnvironment.dist.isClient()) 29 | return; 30 | 31 | Zume.LOGGER.info("Loading LexZume..."); 32 | 33 | LexZumeConfigScreen.register(); 34 | 35 | Zume.registerImplementation(this, FMLPaths.CONFIGDIR.get()); 36 | if (Zume.disabled) 37 | return; 38 | 39 | //noinspection removal 40 | FMLJavaModLoadingContext.get().getModEventBus().addListener(this::registerKeyBindings); 41 | MinecraftForge.EVENT_BUS.addListener(this::render); 42 | MinecraftForge.EVENT_BUS.addListener(EventPriority.LOWEST, this::calculateFOV); 43 | MinecraftForge.EVENT_BUS.addListener(EventPriority.HIGHEST, this::onMouseScroll); 44 | 45 | if (Refraction.safe().getClassOrNull("org.embeddedt.embeddium.api.OptionGUIConstructionEvent") != null && 46 | Refraction.safe().getClassOrNull("me.jellysquid.mods.sodium.client.gui.options.OptionPage") != null) { 47 | new ZumeEmbeddiumConfigScreen(); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean isZoomPressed() { 53 | return Minecraft.getInstance().screen == null && ZumeKeyBind.ZOOM.isPressed(); 54 | } 55 | 56 | @Override 57 | public boolean isZoomInPressed() { 58 | return ZumeKeyBind.ZOOM_IN.isPressed(); 59 | } 60 | 61 | @Override 62 | public boolean isZoomOutPressed() { 63 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 64 | } 65 | 66 | @Override 67 | public CameraPerspective getCameraPerspective() { 68 | return CameraPerspective.values()[Minecraft.getInstance().options.getCameraType().ordinal()]; 69 | } 70 | 71 | private void registerKeyBindings(RegisterKeyMappingsEvent event) { 72 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 73 | event.register(keyBind.value); 74 | } 75 | } 76 | 77 | private void render(TickEvent.RenderTickEvent event) { 78 | if (event.phase == TickEvent.Phase.START) { 79 | Zume.renderHook(); 80 | } 81 | } 82 | 83 | private void calculateFOV(ViewportEvent.ComputeFov event) { 84 | if (Zume.isFOVHookActive()) { 85 | event.setFOV(Zume.fovHook(event.getFOV())); 86 | } 87 | } 88 | 89 | private static final MethodHandle GET_SCROLL_DELTA = Refraction.firstNonNull( 90 | Refraction.safe().getMethodOrNull(InputEvent.MouseScrollingEvent.class, "getScrollDelta"), 91 | Refraction.safe().getMethodOrNull(InputEvent.MouseScrollingEvent.class, "getDeltaY") 92 | ); 93 | 94 | private void onMouseScroll(InputEvent.MouseScrollingEvent event) { 95 | //noinspection DataFlowIssue 96 | if (Zume.mouseScrollHook((int) (double) GET_SCROLL_DELTA.invokeExact(event))) { 97 | event.setCanceled(true); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /lexforge/src/main/java/dev/nolij/zume/lexforge/LexZumeConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.screens.Screen; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraftforge.client.ConfigScreenHandler; 8 | import net.minecraftforge.fml.ModLoadingContext; 9 | 10 | final class LexZumeConfigScreen { 11 | 12 | static void register() { 13 | //noinspection removal 14 | ModLoadingContext.get().registerExtensionPoint( 15 | ConfigScreenHandler.ConfigScreenFactory.class, 16 | () -> new ConfigScreenHandler.ConfigScreenFactory((minecraft, parent) -> new Screen(Component.nullToEmpty(null)) { 17 | @Override 18 | public void tick() { 19 | Zume.openConfigFile(); 20 | Minecraft.getInstance().setScreen(parent); 21 | } 22 | })); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lexforge/src/main/java/dev/nolij/zume/lexforge/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import net.minecraft.client.KeyMapping; 5 | import org.lwjgl.glfw.GLFW; 6 | 7 | public enum ZumeKeyBind { 8 | 9 | ZOOM("zume.zoom", GLFW.GLFW_KEY_Z), 10 | ZOOM_IN("zume.zoom_in", GLFW.GLFW_KEY_EQUAL), 11 | ZOOM_OUT("zume.zoom_out", GLFW.GLFW_KEY_MINUS), 12 | 13 | ; 14 | 15 | public final KeyMapping value; 16 | 17 | public boolean isPressed() { 18 | return value.isDown(); 19 | } 20 | 21 | ZumeKeyBind(String translationKey, int code) { 22 | this.value = new KeyMapping(translationKey, InputConstants.Type.KEYSYM, code, "zume"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lexforge/src/main/java/dev/nolij/zume/mixin/lexforge/CameraMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.lexforge; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Camera; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.ModifyArg; 8 | 9 | @Mixin(value = Camera.class, priority = 1500) 10 | public abstract class CameraMixin { 11 | 12 | @ModifyArg(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getMaxZoom(D)D")) 13 | public double zume$setup$getMaxZoom(double original) { 14 | return Zume.thirdPersonCameraHook(original); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lexforge/src/main/java/dev/nolij/zume/mixin/lexforge/MouseHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.lexforge; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.MouseHandler; 5 | import net.minecraft.client.OptionInstance; 6 | import net.minecraft.client.Options; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Redirect; 10 | 11 | @Mixin(MouseHandler.class) 12 | public abstract class MouseHandlerMixin { 13 | 14 | @Redirect(method = "turnPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;smoothCamera:Z")) 15 | public boolean zume$updateMouse$smoothCameraEnabled(Options instance) { 16 | return Zume.cinematicCameraEnabledHook(instance.smoothCamera); 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | @Redirect(method = "turnPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/OptionInstance;get()Ljava/lang/Object;", ordinal = 0)) 21 | public T zume$updateMouse$getMouseSensitivity$getValue(OptionInstance instance) { 22 | return (T) (Object) Zume.mouseSensitivityHook(instance.get()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lexforge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[1,)" 3 | license="OSL-3.0" 4 | issueTrackerURL="${issue_url}" 5 | 6 | [[mods]] 7 | modId="zume" 8 | version="${mod_version}" 9 | displayName="${mod_name}" 10 | displayURL="${mod_url}" 11 | logoFile="icon.png" 12 | authors="${nolij}" 13 | description=""" 14 | ${mod_description} 15 | """ 16 | 17 | [[dependencies.zume]] 18 | modId="minecraft" 19 | mandatory=true 20 | versionRange="${lexforge_minecraft_range}" 21 | ordering="NONE" 22 | side="CLIENT" 23 | 24 | [[dependencies.zume]] 25 | modId="embeddiumplus" 26 | mandatory=false 27 | versionRange="[1.2.6,)" 28 | ordering="BEFORE" 29 | side="CLIENT" 30 | -------------------------------------------------------------------------------- /lexforge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Zume", 4 | "pack_format": 6 5 | } 6 | } -------------------------------------------------------------------------------- /lexforge/src/main/resources/zume-lexforge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "lexforge.CameraMixin", 8 | "lexforge.MouseHandlerMixin" 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | }, 13 | "refmap": "zume-lexforge-refmap.json" 14 | } 15 | -------------------------------------------------------------------------------- /lexforge16/build.gradle.kts: -------------------------------------------------------------------------------- 1 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 2 | 3 | unimined.minecraft { 4 | version("lexforge16_minecraft_version"()) 5 | 6 | minecraftForge { 7 | loader("lexforge16_version"()) 8 | mixinConfig("zume-lexforge16.mixins.json") 9 | } 10 | 11 | mappings { 12 | searge() 13 | mojmap() 14 | parchment(mcVersion = "lexforge16_minecraft_version"(), version = "lexforge16_parchment_version"()) 15 | } 16 | } -------------------------------------------------------------------------------- /lexforge16/src/main/java/dev/nolij/zume/lexforge16/LexZume16.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge16; 2 | 3 | import cpw.mods.modlauncher.api.INameMappingService; 4 | import dev.nolij.libnolij.refraction.Refraction; 5 | import dev.nolij.zume.impl.CameraPerspective; 6 | import dev.nolij.zume.impl.IZumeImplementation; 7 | import dev.nolij.zume.impl.Zume; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.Options; 10 | import net.minecraftforge.client.event.EntityViewRenderEvent; 11 | import net.minecraftforge.client.event.InputEvent; 12 | import net.minecraftforge.common.MinecraftForge; 13 | import net.minecraftforge.event.TickEvent; 14 | import net.minecraftforge.eventbus.api.EventPriority; 15 | import net.minecraftforge.fml.client.registry.ClientRegistry; 16 | import net.minecraftforge.fml.common.Mod; 17 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 18 | import net.minecraftforge.fml.loading.FMLEnvironment; 19 | import net.minecraftforge.fml.loading.FMLPaths; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodType; 23 | 24 | import static dev.nolij.zume.impl.ZumeConstants.MOD_ID; 25 | 26 | @Mod(MOD_ID) 27 | public class LexZume16 implements IZumeImplementation { 28 | 29 | public LexZume16() { 30 | if (!FMLEnvironment.dist.isClient()) 31 | return; 32 | 33 | Zume.LOGGER.info("Loading LexZume16..."); 34 | 35 | LexZume16ConfigScreen.register(); 36 | 37 | Zume.registerImplementation(this, FMLPaths.CONFIGDIR.get()); 38 | if (Zume.disabled) 39 | return; 40 | 41 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 42 | ClientRegistry.registerKeyBinding(keyBind.value); 43 | } 44 | 45 | MinecraftForge.EVENT_BUS.addListener(this::render); 46 | MinecraftForge.EVENT_BUS.addListener(EventPriority.LOWEST, this::calculateFOV); 47 | MinecraftForge.EVENT_BUS.addListener(EventPriority.HIGHEST, this::onMouseScroll); 48 | } 49 | 50 | @Override 51 | public boolean isZoomPressed() { 52 | return Minecraft.getInstance().screen == null && ZumeKeyBind.ZOOM.isPressed(); 53 | } 54 | 55 | @Override 56 | public boolean isZoomInPressed() { 57 | return ZumeKeyBind.ZOOM_IN.isPressed(); 58 | } 59 | 60 | @Override 61 | public boolean isZoomOutPressed() { 62 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 63 | } 64 | 65 | private static final MethodHandle GET_CAMERA_TYPE = Refraction.safe().getMethodOrNull( 66 | Options.class, 67 | ObfuscationReflectionHelper.remapName(INameMappingService.Domain.METHOD, "func_243230_g"), 68 | MethodType.methodType(Enum.class, Options.class) 69 | ); 70 | private static final MethodHandle THIRD_PERSON_VIEW = 71 | Refraction.safe().getGetterOrNull(Options.class, "field_74320_O", int.class); 72 | 73 | @Override 74 | public CameraPerspective getCameraPerspective() { 75 | int ordinal; 76 | if (GET_CAMERA_TYPE != null) 77 | ordinal = ((Enum) GET_CAMERA_TYPE.invokeExact(Minecraft.getInstance().options)).ordinal(); 78 | else 79 | //noinspection DataFlowIssue 80 | ordinal = (int) THIRD_PERSON_VIEW.invokeExact(Minecraft.getInstance().options); 81 | 82 | return CameraPerspective.values()[ordinal]; 83 | } 84 | 85 | private void render(TickEvent.RenderTickEvent event) { 86 | if (event.phase == TickEvent.Phase.START) { 87 | Zume.renderHook(); 88 | } 89 | } 90 | 91 | private void calculateFOV(EntityViewRenderEvent.FOVModifier event) { 92 | if (Zume.isFOVHookActive()) { 93 | event.setFOV(Zume.fovHook(event.getFOV())); 94 | } 95 | } 96 | 97 | private void onMouseScroll(InputEvent.MouseScrollEvent event) { 98 | if (Zume.mouseScrollHook((int) event.getScrollDelta())) { 99 | event.setCanceled(true); 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /lexforge16/src/main/java/dev/nolij/zume/lexforge16/LexZume16ConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge16; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.screens.Screen; 6 | import net.minecraft.network.chat.TextComponent; 7 | import net.minecraftforge.fml.ExtensionPoint; 8 | import net.minecraftforge.fml.ModLoadingContext; 9 | 10 | final class LexZume16ConfigScreen { 11 | 12 | static void register() { 13 | ModLoadingContext.get().registerExtensionPoint( 14 | ExtensionPoint.CONFIGGUIFACTORY, 15 | () -> (minecraft, parent) -> new ConfigScreen(parent)); 16 | } 17 | 18 | private static final class ConfigScreen extends Screen { 19 | 20 | private final Screen parent; 21 | 22 | private ConfigScreen(Screen parent) { 23 | super(new TextComponent("")); 24 | this.parent = parent; 25 | 26 | Zume.openConfigFile(); 27 | } 28 | 29 | @Override 30 | protected void init() { 31 | Minecraft.getInstance().setScreen(parent); 32 | } 33 | 34 | @SuppressWarnings("unused") 35 | public void render(int mouseX, int mouseY, float delta) { 36 | init(); 37 | } 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /lexforge16/src/main/java/dev/nolij/zume/lexforge16/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge16; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import net.minecraft.client.KeyMapping; 5 | import org.lwjgl.glfw.GLFW; 6 | 7 | public enum ZumeKeyBind { 8 | 9 | ZOOM("zume.zoom", GLFW.GLFW_KEY_Z), 10 | ZOOM_IN("zume.zoom_in", GLFW.GLFW_KEY_EQUAL), 11 | ZOOM_OUT("zume.zoom_out", GLFW.GLFW_KEY_MINUS), 12 | 13 | ; 14 | 15 | public final KeyMapping value; 16 | 17 | public boolean isPressed() { 18 | return value.isDown(); 19 | } 20 | 21 | ZumeKeyBind(String translationKey, int code) { 22 | this.value = new KeyMapping(translationKey, InputConstants.Type.KEYSYM, code, "zume"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lexforge16/src/main/java/dev/nolij/zume/mixin/lexforge16/CameraMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.lexforge16; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Camera; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.ModifyArg; 8 | 9 | @Mixin(value = Camera.class, priority = 1500) 10 | public abstract class CameraMixin { 11 | 12 | @ModifyArg(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getMaxZoom(D)D")) 13 | public double zume$setup$getMaxZoom(double original) { 14 | return Zume.thirdPersonCameraHook(original); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lexforge16/src/main/java/dev/nolij/zume/mixin/lexforge16/MouseHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.lexforge16; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.MouseHandler; 5 | import net.minecraft.client.Options; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Redirect; 9 | 10 | @Mixin(MouseHandler.class) 11 | public abstract class MouseHandlerMixin { 12 | 13 | @Redirect(method = "turnPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;smoothCamera:Z")) 14 | public boolean zume$updateMouse$smoothCameraEnabled(Options instance) { 15 | return Zume.cinematicCameraEnabledHook(instance.smoothCamera); 16 | } 17 | 18 | @Redirect(method = "turnPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;sensitivity:D", ordinal = 0)) 19 | public double zume$updateMouse$getMouseSensitivity$getValue(Options instance) { 20 | return Zume.mouseSensitivityHook(instance.sensitivity); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lexforge16/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[1,)" 3 | license="OSL-3.0" 4 | issueTrackerURL="${issue_url}" 5 | 6 | [[mods]] 7 | modId="zume" 8 | version="${mod_version}" 9 | displayName="${mod_name}" 10 | displayURL="${mod_url}" 11 | logoFile="icon.png" 12 | authors="${nolij}" 13 | description=""" 14 | ${mod_description} 15 | """ 16 | 17 | [[dependencies.zume]] 18 | modId="minecraft" 19 | mandatory=true 20 | versionRange="${lexforge16_minecraft_range}" 21 | ordering="NONE" 22 | side="CLIENT" 23 | -------------------------------------------------------------------------------- /lexforge16/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Zume", 4 | "pack_format": 6 5 | } 6 | } -------------------------------------------------------------------------------- /lexforge16/src/main/resources/zume-lexforge16.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "lexforge16.CameraMixin", 8 | "lexforge16.MouseHandlerMixin" 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | }, 13 | "refmap": "zume-lexforge16-refmap.json" 14 | } 15 | -------------------------------------------------------------------------------- /lexforge18/build.gradle.kts: -------------------------------------------------------------------------------- 1 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 2 | 3 | unimined.minecraft { 4 | version("lexforge18_minecraft_version"()) 5 | 6 | minecraftForge { 7 | loader("lexforge18_version"()) 8 | mixinConfig("zume-lexforge18.mixins.json") 9 | } 10 | 11 | mappings { 12 | mojmap() 13 | parchment(mcVersion = "lexforge18_minecraft_version"(), version = "lexforge18_parchment_version"()) 14 | } 15 | } 16 | 17 | dependencies { 18 | compileOnly(project(":stubs")) 19 | } -------------------------------------------------------------------------------- /lexforge18/src/main/java/dev/nolij/zume/lexforge18/LexZume18.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge18; 2 | 3 | import dev.nolij.libnolij.refraction.Refraction; 4 | import dev.nolij.zume.impl.CameraPerspective; 5 | import dev.nolij.zume.impl.IZumeImplementation; 6 | import dev.nolij.zume.impl.Zume; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraftforge.client.ClientRegistry; 9 | import net.minecraftforge.client.event.EntityViewRenderEvent; 10 | import net.minecraftforge.client.event.InputEvent; 11 | import net.minecraftforge.common.MinecraftForge; 12 | import net.minecraftforge.event.TickEvent; 13 | import net.minecraftforge.eventbus.api.EventPriority; 14 | import net.minecraftforge.fml.common.Mod; 15 | import net.minecraftforge.fml.loading.FMLEnvironment; 16 | import net.minecraftforge.fml.loading.FMLPaths; 17 | 18 | import java.lang.invoke.MethodHandle; 19 | import java.lang.invoke.MethodType; 20 | 21 | import static dev.nolij.zume.impl.ZumeConstants.MOD_ID; 22 | 23 | @Mod(MOD_ID) 24 | public class LexZume18 implements IZumeImplementation { 25 | 26 | private static final Class FOV_EVENT_CLASS = Refraction.safe().getClassOrNull( 27 | "net.minecraftforge.client.event.EntityViewRenderEvent$FieldOfView", 28 | "net.minecraftforge.client.event.EntityViewRenderEvent$FOVModifier" 29 | ); 30 | private static final MethodHandle GET_FOV = Refraction.safe().getMethodOrNull( 31 | FOV_EVENT_CLASS, 32 | "getFOV", 33 | MethodType.methodType(double.class, EntityViewRenderEvent.class) 34 | ); 35 | private static final MethodHandle SET_FOV = Refraction.safe().getMethodOrNull( 36 | FOV_EVENT_CLASS, 37 | "setFOV", 38 | MethodType.methodType(void.class, EntityViewRenderEvent.class, double.class), 39 | double.class 40 | ); 41 | 42 | public LexZume18() { 43 | if (!FMLEnvironment.dist.isClient()) 44 | return; 45 | 46 | Zume.LOGGER.info("Loading LexZume18..."); 47 | 48 | LexZume18ConfigScreen.register(); 49 | 50 | Zume.registerImplementation(this, FMLPaths.CONFIGDIR.get()); 51 | if (Zume.disabled) 52 | return; 53 | 54 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 55 | ClientRegistry.registerKeyBinding(keyBind.value); 56 | } 57 | 58 | MinecraftForge.EVENT_BUS.addListener(this::render); 59 | MinecraftForge.EVENT_BUS.addListener(EventPriority.LOWEST, this::calculateFOV); 60 | MinecraftForge.EVENT_BUS.addListener(EventPriority.HIGHEST, this::onMouseScroll); 61 | } 62 | 63 | @Override 64 | public boolean isZoomPressed() { 65 | return Minecraft.getInstance().screen == null && ZumeKeyBind.ZOOM.isPressed(); 66 | } 67 | 68 | @Override 69 | public boolean isZoomInPressed() { 70 | return ZumeKeyBind.ZOOM_IN.isPressed(); 71 | } 72 | 73 | @Override 74 | public boolean isZoomOutPressed() { 75 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 76 | } 77 | 78 | @Override 79 | public CameraPerspective getCameraPerspective() { 80 | return CameraPerspective.values()[Minecraft.getInstance().options.getCameraType().ordinal()]; 81 | } 82 | 83 | private void render(TickEvent.RenderTickEvent event) { 84 | if (event.phase == TickEvent.Phase.START) { 85 | Zume.renderHook(); 86 | } 87 | } 88 | 89 | private void calculateFOV(EntityViewRenderEvent event) { 90 | if (event.getClass() == FOV_EVENT_CLASS && Zume.isFOVHookActive()) { 91 | //noinspection DataFlowIssue 92 | SET_FOV.invokeExact(event, Zume.fovHook((double) GET_FOV.invokeExact(event))); 93 | } 94 | } 95 | 96 | private void onMouseScroll(InputEvent.MouseScrollEvent event) { 97 | if (Zume.mouseScrollHook((int) event.getScrollDelta())) { 98 | event.setCanceled(true); 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /lexforge18/src/main/java/dev/nolij/zume/lexforge18/LexZume18ConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge18; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.screens.Screen; 6 | import net.minecraft.network.chat.TextComponent; 7 | import net.minecraftforge.client.ConfigGuiHandler; 8 | import net.minecraftforge.fml.ModLoadingContext; 9 | 10 | final class LexZume18ConfigScreen { 11 | 12 | static void register() { 13 | ModLoadingContext.get().registerExtensionPoint( 14 | ConfigGuiHandler.ConfigGuiFactory.class, 15 | () -> new ConfigGuiHandler.ConfigGuiFactory((minecraft, parent) -> new Screen(new TextComponent("")) { 16 | @Override 17 | public void tick() { 18 | Zume.openConfigFile(); 19 | Minecraft.getInstance().setScreen(parent); 20 | } 21 | })); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /lexforge18/src/main/java/dev/nolij/zume/lexforge18/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.lexforge18; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import net.minecraft.client.KeyMapping; 5 | import org.lwjgl.glfw.GLFW; 6 | 7 | public enum ZumeKeyBind { 8 | 9 | ZOOM("zume.zoom", GLFW.GLFW_KEY_Z), 10 | ZOOM_IN("zume.zoom_in", GLFW.GLFW_KEY_EQUAL), 11 | ZOOM_OUT("zume.zoom_out", GLFW.GLFW_KEY_MINUS), 12 | 13 | ; 14 | 15 | public final KeyMapping value; 16 | 17 | public boolean isPressed() { 18 | return value.isDown(); 19 | } 20 | 21 | ZumeKeyBind(String translationKey, int code) { 22 | this.value = new KeyMapping(translationKey, InputConstants.Type.KEYSYM, code, "zume"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lexforge18/src/main/java/dev/nolij/zume/mixin/lexforge18/CameraMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.lexforge18; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Camera; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.ModifyArg; 8 | 9 | @Mixin(value = Camera.class, priority = 1500) 10 | public abstract class CameraMixin { 11 | 12 | @ModifyArg(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getMaxZoom(D)D")) 13 | public double zume$setup$getMaxZoom(double original) { 14 | return Zume.thirdPersonCameraHook(original); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lexforge18/src/main/java/dev/nolij/zume/mixin/lexforge18/MouseHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.lexforge18; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.MouseHandler; 5 | import net.minecraft.client.Options; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Redirect; 9 | 10 | @Mixin(MouseHandler.class) 11 | public abstract class MouseHandlerMixin { 12 | 13 | @Redirect(method = "turnPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;smoothCamera:Z")) 14 | public boolean zume$updateMouse$smoothCameraEnabled(Options instance) { 15 | return Zume.cinematicCameraEnabledHook(instance.smoothCamera); 16 | } 17 | 18 | @Redirect(method = "turnPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;sensitivity:D", ordinal = 0)) 19 | public double zume$updateMouse$getMouseSensitivity$getValue(Options instance) { 20 | return Zume.mouseSensitivityHook(instance.sensitivity); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lexforge18/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[1,)" 3 | license="OSL-3.0" 4 | issueTrackerURL="${issue_url}" 5 | 6 | [[mods]] 7 | modId="zume" 8 | version="${mod_version}" 9 | displayName="${mod_name}" 10 | displayURL="${mod_url}" 11 | logoFile="icon.png" 12 | authors="${nolij}" 13 | description=""" 14 | ${mod_description} 15 | """ 16 | 17 | [[dependencies.zume]] 18 | modId="minecraft" 19 | mandatory=true 20 | versionRange="${lexforge18_minecraft_range}" 21 | ordering="NONE" 22 | side="CLIENT" 23 | -------------------------------------------------------------------------------- /lexforge18/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Zume", 4 | "pack_format": 6 5 | } 6 | } -------------------------------------------------------------------------------- /lexforge18/src/main/resources/zume-lexforge18.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "lexforge18.CameraMixin", 8 | "lexforge18.MouseHandlerMixin" 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | }, 13 | "refmap": "zume-lexforge18-refmap.json" 14 | } 15 | -------------------------------------------------------------------------------- /modern/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import xyz.wagyourtail.unimined.api.minecraft.task.RemapJarTask 2 | 3 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 4 | 5 | val modCompileOnly: Configuration by configurations.creating { 6 | configurations.compileClasspath.get().extendsFrom(this) 7 | } 8 | val modRuntimeOnly: Configuration by configurations.creating { 9 | configurations.runtimeClasspath.get().extendsFrom(this) 10 | } 11 | val mod: Configuration by configurations.creating { 12 | configurations.compileClasspath.get().extendsFrom(this) 13 | configurations.runtimeClasspath.get().extendsFrom(this) 14 | } 15 | 16 | unimined.minecraft { 17 | combineWith(project(":integration:embeddium").sourceSets.main.get()) 18 | 19 | version("modern_minecraft_version"()) 20 | 21 | fabric { 22 | loader("fabric_version"()) 23 | } 24 | 25 | mappings { 26 | intermediary() 27 | mojmap() 28 | devFallbackNamespace("intermediary") 29 | } 30 | 31 | mods { 32 | remap(modCompileOnly) 33 | remap(modRuntimeOnly) 34 | remap(mod) 35 | } 36 | } 37 | 38 | repositories { 39 | maven("https://maven.terraformersmc.com/releases/") 40 | maven("https://maven.blamejared.com") 41 | } 42 | 43 | dependencies { 44 | compileOnly(project(":stubs")) 45 | 46 | modCompileOnly(fabricApi.fabricModule("fabric-key-binding-api-v1", "modern_fabric_api_version"())) 47 | modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${"modern_fabric_api_version"()}") 48 | 49 | mod("com.terraformersmc:modmenu:7.+") 50 | 51 | modRuntimeOnly("org.embeddedt:embeddium-fabric-1.20.1:${"embeddium_fabric_version"()}") 52 | } -------------------------------------------------------------------------------- /modern/src/main/java/dev/nolij/zume/mixin/modern/CameraMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.modern; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Camera; 5 | import org.spongepowered.asm.mixin.Dynamic; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Group; 9 | import org.spongepowered.asm.mixin.injection.ModifyArg; 10 | 11 | @Mixin(value = Camera.class, priority = 1500) 12 | public abstract class CameraMixin { 13 | 14 | @Group(name = "zume$thirdPersonCameraHook", min = 1, max = 1) 15 | @ModifyArg(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getMaxZoom(D)D"), require = 0) 16 | public double zume$update$clipToSpace(double original) { 17 | return Zume.thirdPersonCameraHook(original); 18 | } 19 | 20 | @Dynamic 21 | @Group(name = "zume$thirdPersonCameraHook", min = 1, max = 1) 22 | @ModifyArg(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/class_4184;method_19318(F)F", remap = false), require = 0) 23 | public float zume$update$clipToSpace(float original) { 24 | return (float) Zume.thirdPersonCameraHook(original); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /modern/src/main/java/dev/nolij/zume/mixin/modern/GameRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.modern; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 4 | import dev.nolij.zume.impl.Zume; 5 | import net.minecraft.client.renderer.GameRenderer; 6 | import org.spongepowered.asm.mixin.Dynamic; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Group; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(GameRenderer.class) 14 | public abstract class GameRendererMixin { 15 | 16 | @Dynamic 17 | @Inject(method = {"render", "method_3192(Lnet/minecraft/class_9779;Z)V"}, at = @At("HEAD")) 18 | public void zume$render$HEAD(CallbackInfo ci) { 19 | Zume.renderHook(); 20 | } 21 | 22 | @Group(name = "zume$getFov", min = 1, max = 1) 23 | @ModifyReturnValue(method = "getFov", at = @At("TAIL"), require = 0) 24 | public double zume$getFov$TAIL(double original) { 25 | if (Zume.isFOVHookActive()) 26 | return Zume.fovHook(original); 27 | 28 | return original; 29 | } 30 | 31 | // 24w33a (21.2)+ compat 32 | @Dynamic 33 | @Group(name = "zume$getFov", min = 1, max = 1) 34 | @ModifyReturnValue(method = "method_3196(Lnet/minecraft/class_4184;FZ)F", at = @At("TAIL"), require = 0) 35 | public float zume$getFov$TAIL(float original) { 36 | if (Zume.isFOVHookActive()) 37 | return (float) Zume.fovHook(original); 38 | 39 | return original; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /modern/src/main/java/dev/nolij/zume/mixin/modern/MouseHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.modern; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 5 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 6 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 7 | import dev.nolij.zume.impl.Zume; 8 | import net.minecraft.client.MouseHandler; 9 | import net.minecraft.world.entity.player.Inventory; 10 | import org.spongepowered.asm.mixin.Dynamic; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Group; 14 | 15 | @Mixin(value = MouseHandler.class, priority = 500) 16 | public abstract class MouseHandlerMixin { 17 | 18 | @Dynamic 19 | @ModifyExpressionValue(method = { 20 | "turnPlayer", 21 | "method_1606(D)V" // 20.5+ compat 22 | }, at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;smoothCamera:Z")) 23 | public boolean zume$updateMouse$smoothCameraEnabled(boolean original) { 24 | return Zume.cinematicCameraEnabledHook(original); 25 | } 26 | 27 | @SuppressWarnings("unchecked") 28 | @Dynamic 29 | @Group(name = "zume$getMouseSensitivity", min = 1, max = 1) 30 | @ModifyExpressionValue(method = { 31 | "turnPlayer", 32 | "method_1606(D)V" // 20.5+ compat 33 | }, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/OptionInstance;get()Ljava/lang/Object;", ordinal = 0), require = 0) 34 | public T zume$updateMouse$getMouseSensitivity$getValue(T original) { 35 | return (T) (Object) Zume.mouseSensitivityHook((Double) original); 36 | } 37 | 38 | @Dynamic 39 | @Group(name = "zume$getMouseSensitivity", min = 1, max = 1) 40 | @ModifyExpressionValue(method = "turnPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/class_315;field_1843:D", remap = false), require = 0) 41 | public double zume$updateMouse$mouseSensitivity(double original) { 42 | return Zume.mouseSensitivityHook(original); 43 | } 44 | 45 | @ModifyExpressionValue(method = "onScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isSpectator()Z")) 46 | public boolean onMouseScroll$isSpectator(boolean original) { 47 | if (Zume.isMouseScrollHookActive()) 48 | return false; 49 | 50 | return original; 51 | } 52 | 53 | @Group(name = "zume$onScroll", min = 1, max = 1) 54 | @WrapWithCondition(method = "onScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;swapPaint(D)V"), require = 0) 55 | public boolean onMouseScroll$scrollInHotbar(Inventory instance, double scrollAmount) { 56 | return !Zume.mouseScrollHook((int) scrollAmount); 57 | } 58 | 59 | // 24w33a (21.2)+ compat 60 | @Dynamic 61 | @Group(name = "zume$onScroll", min = 1, max = 1) 62 | @WrapOperation(method = "onScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/class_9928;method_61972(DII)I"), require = 0) 63 | public int zume$onScroll$getNextScrollWheelSelection(double scrollAmount, int current, int max, Operation original) { 64 | if (Zume.mouseScrollHook((int) scrollAmount)) 65 | return current; 66 | 67 | return original.call(scrollAmount, current, max); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /modern/src/main/java/dev/nolij/zume/modern/ModernZume.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.modern; 2 | 3 | import dev.nolij.libnolij.refraction.Refraction; 4 | import dev.nolij.zume.impl.CameraPerspective; 5 | import dev.nolij.zume.impl.IZumeImplementation; 6 | import dev.nolij.zume.impl.Zume; 7 | import dev.nolij.zume.integration.implementation.embeddium.ZumeEmbeddiumConfigScreen; 8 | import net.fabricmc.api.ClientModInitializer; 9 | import net.fabricmc.api.EnvType; 10 | import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; 11 | import net.fabricmc.loader.api.FabricLoader; 12 | import net.minecraft.client.Minecraft; 13 | import net.minecraft.client.Options; 14 | 15 | import java.lang.invoke.MethodHandle; 16 | import java.lang.invoke.MethodType; 17 | 18 | public class ModernZume implements ClientModInitializer, IZumeImplementation { 19 | 20 | @Override 21 | public void onInitializeClient() { 22 | if (FabricLoader.getInstance().getEnvironmentType() != EnvType.CLIENT) 23 | return; 24 | 25 | Zume.LOGGER.info("Loading Modern Zume..."); 26 | 27 | Zume.registerImplementation(this, FabricLoader.getInstance().getConfigDir()); 28 | if (Zume.disabled) 29 | return; 30 | 31 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 32 | KeyBindingHelper.registerKeyBinding(keyBind.value); 33 | } 34 | 35 | if (Refraction.safe().getClassOrNull("org.embeddedt.embeddium.client.gui.options.OptionIdentifier") != null) { 36 | new ZumeEmbeddiumConfigScreen(); 37 | } 38 | } 39 | 40 | @Override 41 | public boolean isZoomPressed() { 42 | return Minecraft.getInstance().screen == null && ZumeKeyBind.ZOOM.isPressed(); 43 | } 44 | 45 | @Override 46 | public boolean isZoomInPressed() { 47 | return ZumeKeyBind.ZOOM_IN.isPressed(); 48 | } 49 | 50 | @Override 51 | public boolean isZoomOutPressed() { 52 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 53 | } 54 | 55 | private static final MethodHandle GET_PERSPECTIVE = Refraction.safe().getMethodOrNull( 56 | Options.class, 57 | FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", 58 | "net.minecraft.class_315", "method_31044", "()Lnet/minecraft/class_5498;"), 59 | MethodType.methodType(Enum.class, Options.class)); 60 | private static final MethodHandle PERSPECTIVE = 61 | Refraction.safe().getGetterOrNull(Options.class, "field_1850", int.class); 62 | 63 | @Override 64 | public CameraPerspective getCameraPerspective() { 65 | int ordinal; 66 | if (GET_PERSPECTIVE != null) 67 | ordinal = ((Enum) GET_PERSPECTIVE.invokeExact(Minecraft.getInstance().options)).ordinal(); 68 | else 69 | //noinspection DataFlowIssue 70 | ordinal = (int) PERSPECTIVE.invokeExact(Minecraft.getInstance().options); 71 | 72 | return CameraPerspective.values()[ordinal]; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /modern/src/main/java/dev/nolij/zume/modern/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.modern; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import net.minecraft.client.KeyMapping; 5 | import org.lwjgl.glfw.GLFW; 6 | 7 | public enum ZumeKeyBind { 8 | 9 | ZOOM("zume.zoom", GLFW.GLFW_KEY_Z), 10 | ZOOM_IN("zume.zoom_in", GLFW.GLFW_KEY_EQUAL), 11 | ZOOM_OUT("zume.zoom_out", GLFW.GLFW_KEY_MINUS), 12 | 13 | ; 14 | 15 | public final KeyMapping value; 16 | 17 | public boolean isPressed() { 18 | return value.isDown(); 19 | } 20 | 21 | ZumeKeyBind(String translationKey, int code) { 22 | this.value = new KeyMapping(translationKey, InputConstants.Type.KEYSYM, code, "zume"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /modern/src/main/java/dev/nolij/zume/modern/integration/modmenu/ModernZumeConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.modern.integration.modmenu; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.screens.Screen; 6 | import net.minecraft.network.chat.Component; 7 | 8 | public class ModernZumeConfigScreen extends Screen { 9 | 10 | private final Screen parent; 11 | 12 | public ModernZumeConfigScreen(Component arg, Screen parent) { 13 | super(arg); 14 | this.parent = parent; 15 | } 16 | 17 | @Override 18 | public void init() { 19 | Zume.openConfigFile(); 20 | 21 | Minecraft.getInstance().setScreen(parent); 22 | } 23 | 24 | @SuppressWarnings("unused") 25 | public void render(int mouseX, int mouseY, float delta) { 26 | init(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /modern/src/main/java/dev/nolij/zume/modern/integration/modmenu/ZumeModMenuIntegration.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.modern.integration.modmenu; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import dev.nolij.libnolij.refraction.Refraction; 5 | import io.github.prospector.modmenu.api.ModMenuApi; 6 | import net.minecraft.client.gui.screens.Screen; 7 | import net.minecraft.network.chat.Component; 8 | 9 | import java.lang.invoke.MethodHandle; 10 | import java.lang.invoke.MethodType; 11 | import java.util.function.Function; 12 | 13 | import static dev.nolij.zume.impl.ZumeConstants.MOD_ID; 14 | 15 | public class ZumeModMenuIntegration implements ModMenuApi { 16 | 17 | private static final MethodHandle LITERALTEXT_INIT = Refraction.safe().getConstructorOrNull( 18 | Refraction.safe().getClassOrNull("net.minecraft.class_2585"), 19 | MethodType.methodType(Component.class, String.class), 20 | String.class); 21 | 22 | @Override 23 | public String getModId() { 24 | return MOD_ID; 25 | } 26 | 27 | @Override 28 | public Function getConfigScreenFactory() { 29 | return (parent) -> { 30 | //noinspection DataFlowIssue 31 | return new ModernZumeConfigScreen((Component) LITERALTEXT_INIT.invokeExact(""), parent); 32 | }; 33 | } 34 | 35 | @Override 36 | public ConfigScreenFactory getModConfigScreenFactory() { 37 | return (parent) -> new ModernZumeConfigScreen(Component.literal(""), parent); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /modern/src/main/java/io/github/prospector/modmenu/api/ModMenuApi.java: -------------------------------------------------------------------------------- 1 | package io.github.prospector.modmenu.api; 2 | 3 | import dev.nolij.zumegradle.proguard.ProGuardKeep; 4 | import net.minecraft.client.gui.screens.Screen; 5 | 6 | import java.util.function.Function; 7 | 8 | @ProGuardKeep 9 | public interface ModMenuApi extends com.terraformersmc.modmenu.api.ModMenuApi { 10 | 11 | String getModId(); 12 | 13 | Function getConfigScreenFactory(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /modern/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "zume", 4 | "version": "${mod_version}", 5 | "name": "${mod_name}", 6 | "description": "${mod_description}", 7 | "authors": [ 8 | "${nolij}" 9 | ], 10 | "contact": { 11 | "website": "${mod_url}", 12 | "repo": "${repo_url}", 13 | "issues": "${issue_url}" 14 | }, 15 | "license": "OSL-3.0", 16 | "icon": "icon.png", 17 | "environment": "client", 18 | "entrypoints": { 19 | "client": [ 20 | "dev.nolij.zume.modern.ModernZume" 21 | ], 22 | "modmenu": [ 23 | "dev.nolij.zume.modern.integration.modmenu.ZumeModMenuIntegration" 24 | ] 25 | }, 26 | "mixins": [ 27 | "zume-modern.mixins.json" 28 | ], 29 | "depends": { 30 | "fabricloader": ">=${fabric_version}", 31 | "fabric-key-binding-api-v1": "*" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /modern/src/main/resources/zume-modern.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "modern.CameraMixin", 8 | "modern.GameRendererMixin", 9 | "modern.MouseHandlerMixin" 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | }, 14 | "refmap": "zume-modern-refmap.json" 15 | } 16 | -------------------------------------------------------------------------------- /neoforge/build.gradle.kts: -------------------------------------------------------------------------------- 1 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 2 | 3 | val modCompileOnly: Configuration by configurations.creating { 4 | configurations.compileClasspath.get().extendsFrom(this) 5 | } 6 | val modRuntimeOnly: Configuration by configurations.creating { 7 | configurations.runtimeClasspath.get().extendsFrom(this) 8 | } 9 | val mod: Configuration by configurations.creating { 10 | configurations.compileClasspath.get().extendsFrom(this) 11 | configurations.runtimeClasspath.get().extendsFrom(this) 12 | } 13 | 14 | unimined.minecraft { 15 | version("neoforge_minecraft_version"()) 16 | 17 | neoForge { 18 | loader("neoforge_version"()) 19 | } 20 | 21 | source { 22 | sourceGenerator.jvmArgs = listOf("-Xmx4G") 23 | } 24 | 25 | mappings { 26 | mojmap() 27 | // parchment(mcVersion = "neoforge_minecraft_version"(), version = "neoforge_parchment_version"()) 28 | } 29 | 30 | mods { 31 | remap(modCompileOnly) 32 | remap(modRuntimeOnly) 33 | remap(mod) 34 | } 35 | } 36 | 37 | repositories { 38 | maven("https://maven.blamejared.com") 39 | } 40 | 41 | dependencies { 42 | compileOnly(project(":stubs")) 43 | 44 | modCompileOnly("org.embeddedt:embeddium-1.21:${"embeddium_neoforge_version"()}:api") 45 | modRuntimeOnly("org.embeddedt:embeddium-1.21:${"embeddium_neoforge_version"()}") { 46 | isTransitive = false 47 | } 48 | } -------------------------------------------------------------------------------- /neoforge/src/main/java/dev/nolij/zume/neoforge/NeoZumeConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.neoforge; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.screens.Screen; 6 | import net.minecraft.network.chat.Component; 7 | 8 | final class NeoZumeConfigScreen extends Screen { 9 | 10 | private final Screen parent; 11 | 12 | public NeoZumeConfigScreen(Screen parent) { 13 | super(Component.empty()); 14 | this.parent = parent; 15 | } 16 | 17 | @Override 18 | public void init() { 19 | Zume.openConfigFile(); 20 | Minecraft.getInstance().setScreen(parent); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /neoforge/src/main/java/dev/nolij/zume/neoforge/NeoZumeConfigScreenFactory.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.neoforge; 2 | 3 | import dev.nolij.zumegradle.proguard.ProGuardKeep; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.screens.Screen; 6 | import net.neoforged.fml.ModContainer; 7 | import net.neoforged.neoforge.client.gui.IConfigScreenFactory; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class NeoZumeConfigScreenFactory implements IConfigScreenFactory { 11 | 12 | @ProGuardKeep 13 | public @NotNull Screen createScreen(@NotNull Minecraft minecraft, @NotNull Screen parent) { 14 | return new NeoZumeConfigScreen(parent); 15 | } 16 | 17 | @Override 18 | public @NotNull Screen createScreen(@NotNull ModContainer modContainer, @NotNull Screen parent) { 19 | return new NeoZumeConfigScreen(parent); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /neoforge/src/main/java/dev/nolij/zume/neoforge/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.neoforge; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import net.minecraft.client.KeyMapping; 5 | import org.lwjgl.glfw.GLFW; 6 | 7 | public enum ZumeKeyBind { 8 | 9 | ZOOM("zume.zoom", GLFW.GLFW_KEY_Z), 10 | ZOOM_IN("zume.zoom_in", GLFW.GLFW_KEY_EQUAL), 11 | ZOOM_OUT("zume.zoom_out", GLFW.GLFW_KEY_MINUS), 12 | 13 | ; 14 | 15 | public final KeyMapping value; 16 | 17 | public boolean isPressed() { 18 | return value.isDown(); 19 | } 20 | 21 | ZumeKeyBind(String translationKey, int code) { 22 | this.value = new KeyMapping(translationKey, InputConstants.Type.KEYSYM, code, "zume"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /neoforge/src/main/java/dev/nolij/zume/neoforge/integration/embeddium/ZumeEmbeddiumConfigScreen.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.neoforge.integration.embeddium; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.minecraft.network.chat.Component; 5 | import org.embeddedt.embeddium.api.OptionGUIConstructionEvent; 6 | import org.embeddedt.embeddium.api.eventbus.EventHandlerRegistrar; 7 | import org.embeddedt.embeddium.api.options.control.Control; 8 | import org.embeddedt.embeddium.api.options.control.ControlValueFormatter; 9 | import org.embeddedt.embeddium.api.options.control.SliderControl; 10 | import org.embeddedt.embeddium.api.options.control.TickBoxControl; 11 | import org.embeddedt.embeddium.api.options.structure.Option; 12 | import org.embeddedt.embeddium.api.options.structure.OptionFlag; 13 | import org.embeddedt.embeddium.api.options.structure.OptionGroup; 14 | import org.embeddedt.embeddium.api.options.structure.OptionImpl; 15 | import org.embeddedt.embeddium.api.options.structure.OptionPage; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import static dev.nolij.zume.neoforge.integration.embeddium.ZumeOptionsStorage.*; 21 | 22 | public class ZumeEmbeddiumConfigScreen implements EventHandlerRegistrar.Handler { 23 | 24 | private Control percentageControl(final Option option) { 25 | return new SliderControl(option, 0, 100, 1, ControlValueFormatter.percentage()); 26 | } 27 | 28 | private Control exponentControl(final Option option) { 29 | return new SliderControl(option, 100, 500, 25, v -> switch (v) { 30 | case 100 -> Component.translatable("zume.linear"); 31 | case 200 -> Component.translatable("zume.quad"); 32 | case 300 -> Component.translatable("zume.cubic"); 33 | case 400 -> Component.translatable("zume.quart"); 34 | case 500 -> Component.translatable("zume.quint"); 35 | default -> Component.literal("x^" + (v / 100D)); 36 | }); 37 | } 38 | 39 | public ZumeEmbeddiumConfigScreen() { 40 | OptionGUIConstructionEvent.BUS.addListener(this); 41 | } 42 | 43 | @Override 44 | public void acceptEvent(OptionGUIConstructionEvent event) { 45 | final ZumeOptionsStorage storage = new ZumeOptionsStorage(); 46 | 47 | final List generalGroups = new ArrayList<>(); 48 | final List advancedGroups = new ArrayList<>(); 49 | 50 | generalGroups.add(OptionGroup.createBuilder() 51 | .setId(BEHAVIOUR) 52 | .add(OptionImpl.createBuilder(int.class, storage) 53 | .setId(ZOOM_SPEED) 54 | .setControl(option -> new SliderControl(option, 5, 50, 5, ControlValueFormatter.number())) 55 | .setBinding( 56 | (config, value) -> config.zoomSpeed = value.shortValue(), 57 | config -> (int) config.zoomSpeed) 58 | .build()) 59 | .add(OptionImpl.createBuilder(boolean.class, storage) 60 | .setId(ENABLE_ZOOM_SCROLLING) 61 | .setControl(TickBoxControl::new) 62 | .setBinding( 63 | (config, value) -> config.isZoomScrollingEnabled = value, 64 | config -> config.isZoomScrollingEnabled) 65 | .build()) 66 | .add(OptionImpl.createBuilder(boolean.class, storage) 67 | .setId(ENABLE_CINEMATIC_ZOOM) 68 | .setControl(TickBoxControl::new) 69 | .setBinding( 70 | (config, value) -> config.isCinematicZoomEnabled = value, 71 | config -> config.isCinematicZoomEnabled) 72 | .build()) 73 | .add(OptionImpl.createBuilder(int.class, storage) 74 | .setId(MOUSE_SENSITIVITY_FLOOR) 75 | .setControl(this::percentageControl) 76 | .setBinding( 77 | (config, value) -> config.mouseSensitivityFloor = Math.max(value * 0.01D, 0.01D), 78 | config -> (int) Math.round(config.mouseSensitivityFloor * 100D)) 79 | .build()) 80 | .add(OptionImpl.createBuilder(int.class, storage) 81 | .setId(DEFAULT_ZOOM) 82 | .setControl(this::percentageControl) 83 | .setBinding( 84 | (config, value) -> config.defaultZoom = value * 0.01D, 85 | config -> (int) Math.round(config.defaultZoom * 100D)) 86 | .build()) 87 | .add(OptionImpl.createBuilder(boolean.class, storage) 88 | .setId(FIRST_PERSON_TOGGLE_MODE) 89 | .setControl(TickBoxControl::new) 90 | .setBinding( 91 | (config, value) -> config.isFirstPersonToggleModeEnabled = value, 92 | config -> config.isFirstPersonToggleModeEnabled) 93 | .build()) 94 | .add(OptionImpl.createBuilder(boolean.class, storage) 95 | .setId(THIRD_PERSON_TOGGLE_MODE) 96 | .setControl(TickBoxControl::new) 97 | .setBinding( 98 | (config, value) -> config.isThirdPersonToggleModeEnabled = value, 99 | config -> config.isThirdPersonToggleModeEnabled) 100 | .build()) 101 | .build()); 102 | generalGroups.add(OptionGroup.createBuilder() 103 | .setId(ANIMATIONS) 104 | .add(OptionImpl.createBuilder(int.class, storage) 105 | .setId(ZOOM_SMOOTHNESS_MS) 106 | .setControl(option -> 107 | new SliderControl(option, 0, 500, 25, 108 | v -> v > 0 109 | ? Component.literal(v + "ms") 110 | : Component.translatable("zume.instant"))) 111 | .setBinding( 112 | (config, value) -> config.zoomSmoothnessMilliseconds = value.shortValue(), 113 | config -> (int) config.zoomSmoothnessMilliseconds) 114 | .build()) 115 | .build()); 116 | generalGroups.add(OptionGroup.createBuilder() 117 | .setId(THIRD_PERSON) 118 | .add(OptionImpl.createBuilder(int.class, storage) 119 | .setId(MAX_THIRD_PERSON_ZOOM_BLOCKS) 120 | .setControl(option -> 121 | new SliderControl(option, 0, 30, 1, 122 | v -> v > 0 123 | ? Component.translatable("zume.blocks", v) 124 | : Component.translatable("zume.disabled"))) 125 | .setBinding( 126 | (config, value) -> config.maximumThirdPersonZoomBlocks = value, 127 | config -> (int) config.maximumThirdPersonZoomBlocks) 128 | .build()) 129 | .add(OptionImpl.createBuilder(int.class, storage) 130 | .setId(MIN_THIRD_PERSON_ZOOM_BLOCKS) 131 | .setControl(option -> 132 | new SliderControl(option, 0, 10, 1, 133 | v -> Component.translatable("zume.blocks", v > 0 ? v : 0.5))) 134 | .setBinding( 135 | (config, value) -> config.minimumThirdPersonZoomBlocks = value > 0 ? value : 0.5, 136 | config -> (int) config.minimumThirdPersonZoomBlocks) 137 | .build()) 138 | .build()); 139 | 140 | advancedGroups.add(OptionGroup.createBuilder() 141 | .setId(EXPONENTS) 142 | .add(OptionImpl.createBuilder(int.class, storage) 143 | .setId(ANIMATION_EASING_EXPONENT) 144 | .setControl(this::exponentControl) 145 | .setBinding( 146 | (config, value) -> config.animationEasingExponent = value / 100D, 147 | config -> (int) Math.round(config.animationEasingExponent * 100)) 148 | .build()) 149 | .add(OptionImpl.createBuilder(int.class, storage) 150 | .setId(ZOOM_EASING_EXPONENT) 151 | .setControl(this::exponentControl) 152 | .setBinding( 153 | (config, value) -> config.zoomEasingExponent = value / 100D, 154 | config -> (int) Math.round(config.zoomEasingExponent * 100)) 155 | .build()) 156 | .build()); 157 | advancedGroups.add(OptionGroup.createBuilder() 158 | .setId(MISC) 159 | .add(OptionImpl.createBuilder(int.class, storage) 160 | .setId(MIN_FOV) 161 | .setControl(option -> 162 | new SliderControl(option, -2, 1, 1, 163 | v -> Component.literal(String.valueOf(Math.pow(10, v))))) 164 | .setBinding( 165 | (config, value) -> config.minimumFOV = Math.pow(10, value), 166 | config -> (int) Math.log10(config.minimumFOV)) 167 | .build()) 168 | .add(OptionImpl.createBuilder(boolean.class, storage) 169 | .setId(DISABLE) 170 | .setControl(TickBoxControl::new) 171 | .setBinding( 172 | (config, value) -> config.isDisabled = value, 173 | config -> config.isDisabled) 174 | .setFlags(OptionFlag.REQUIRES_GAME_RESTART) 175 | .build()) 176 | .build()); 177 | 178 | event.getPages().add(new OptionPage( 179 | GENERAL, 180 | Component.translatable("zume.options.pages.general"), 181 | ImmutableList.copyOf(generalGroups))); 182 | event.getPages().add(new OptionPage( 183 | ADVANCED, 184 | Component.translatable("zume.options.pages.advanced"), 185 | ImmutableList.copyOf(advancedGroups))); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /neoforge/src/main/java/dev/nolij/zume/neoforge/integration/embeddium/ZumeOptionsStorage.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.neoforge.integration.embeddium; 2 | 3 | import dev.nolij.zume.api.config.v1.ZumeConfig; 4 | import dev.nolij.zume.api.config.v1.ZumeConfigAPI; 5 | import dev.nolij.zume.impl.ZumeConstants; 6 | import org.embeddedt.embeddium.api.options.OptionIdentifier; 7 | import org.embeddedt.embeddium.api.options.structure.OptionStorage; 8 | 9 | public final class ZumeOptionsStorage implements OptionStorage { 10 | 11 | //region Pages 12 | public static final OptionIdentifier GENERAL = 13 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general"); 14 | public static final OptionIdentifier ADVANCED = 15 | OptionIdentifier.create(ZumeConstants.MOD_ID, "advanced"); 16 | //endregion 17 | 18 | //region Groups 19 | public static final OptionIdentifier BEHAVIOUR = 20 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general/behaviour"); 21 | public static final OptionIdentifier ANIMATIONS = 22 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general/animations"); 23 | public static final OptionIdentifier THIRD_PERSON = 24 | OptionIdentifier.create(ZumeConstants.MOD_ID, "general/third_person"); 25 | 26 | public static final OptionIdentifier EXPONENTS = 27 | OptionIdentifier.create(ZumeConstants.MOD_ID, "advanced/exponents"); 28 | public static final OptionIdentifier MISC = 29 | OptionIdentifier.create(ZumeConstants.MOD_ID, "advanced/misc"); 30 | //endregion 31 | 32 | //region Options 33 | public static final OptionIdentifier ENABLE_CINEMATIC_ZOOM = 34 | OptionIdentifier.create(ZumeConstants.MOD_ID, "enable_cinematic_zoom", boolean.class); 35 | public static final OptionIdentifier MOUSE_SENSITIVITY_FLOOR = 36 | OptionIdentifier.create(ZumeConstants.MOD_ID, "mouse_sensitivity_floor", int.class); 37 | public static final OptionIdentifier ZOOM_SPEED = 38 | OptionIdentifier.create(ZumeConstants.MOD_ID, "zoom_speed", int.class); 39 | public static final OptionIdentifier ENABLE_ZOOM_SCROLLING = 40 | OptionIdentifier.create(ZumeConstants.MOD_ID, "enable_zoom_scrolling", boolean.class); 41 | public static final OptionIdentifier ZOOM_SMOOTHNESS_MS = 42 | OptionIdentifier.create(ZumeConstants.MOD_ID, "zoom_smoothness_ms", int.class); 43 | public static final OptionIdentifier ANIMATION_EASING_EXPONENT = 44 | OptionIdentifier.create(ZumeConstants.MOD_ID, "animation_easing_exponent", int.class); 45 | public static final OptionIdentifier ZOOM_EASING_EXPONENT = 46 | OptionIdentifier.create(ZumeConstants.MOD_ID, "zoom_easing_exponent", int.class); 47 | public static final OptionIdentifier DEFAULT_ZOOM = 48 | OptionIdentifier.create(ZumeConstants.MOD_ID, "default_zoom", int.class); 49 | public static final OptionIdentifier FIRST_PERSON_TOGGLE_MODE = 50 | OptionIdentifier.create(ZumeConstants.MOD_ID, "first_person_toggle_mode", boolean.class); 51 | public static final OptionIdentifier THIRD_PERSON_TOGGLE_MODE = 52 | OptionIdentifier.create(ZumeConstants.MOD_ID, "third_person_toggle_mode", boolean.class); 53 | public static final OptionIdentifier MIN_FOV = 54 | OptionIdentifier.create(ZumeConstants.MOD_ID, "min_fov", int.class); 55 | public static final OptionIdentifier MAX_THIRD_PERSON_ZOOM_BLOCKS = 56 | OptionIdentifier.create(ZumeConstants.MOD_ID, "max_third_person_zoom_blocks", int.class); 57 | public static final OptionIdentifier MIN_THIRD_PERSON_ZOOM_BLOCKS = 58 | OptionIdentifier.create(ZumeConstants.MOD_ID, "min_third_person_zoom_blocks", int.class); 59 | public static final OptionIdentifier DISABLE = 60 | OptionIdentifier.create(ZumeConstants.MOD_ID, "disable", boolean.class); 61 | //endregion 62 | 63 | private final ZumeConfig storage = ZumeConfigAPI.getSnapshot(); 64 | 65 | @Override 66 | public ZumeConfig getData() { 67 | return storage; 68 | } 69 | 70 | @Override 71 | public void save() { 72 | ZumeConfigAPI.replaceConfig(storage); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[1,)" 3 | license="OSL-3.0" 4 | issueTrackerURL="${issue_url}" 5 | 6 | [[mods]] 7 | modId="zume" 8 | version="${mod_version}" 9 | displayName="${mod_name}" 10 | displayURL="${mod_url}" 11 | logoFile="icon.png" 12 | authors="${nolij}" 13 | description=""" 14 | ${mod_description} 15 | """ 16 | 17 | [[dependencies.zume]] 18 | modId="neoforge" 19 | type="required" 20 | versionRange="${neoforge_neoforge_range}" 21 | ordering="NONE" 22 | side="CLIENT" 23 | 24 | [[dependencies.zume]] 25 | modId="minecraft" 26 | type="required" 27 | versionRange="${neoforge_minecraft_range}" 28 | ordering="NONE" 29 | side="CLIENT" 30 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[1,)" 3 | license="OSL-3.0" 4 | issueTrackerURL="${issue_url}" 5 | 6 | [[mods]] 7 | modId="zume" 8 | version="${mod_version}" 9 | displayName="${mod_name}" 10 | displayURL="${mod_url}" 11 | logoFile="icon.png" 12 | authors="${nolij}" 13 | description=""" 14 | ${mod_description} 15 | """ 16 | 17 | [[dependencies.zume]] 18 | modId="neoforge" 19 | type="required" 20 | versionRange="${neoforge_neoforge_range}" 21 | ordering="NONE" 22 | side="CLIENT" 23 | 24 | [[dependencies.zume]] 25 | modId="minecraft" 26 | type="required" 27 | versionRange="${neoforge_minecraft_range}" 28 | ordering="NONE" 29 | side="CLIENT" 30 | -------------------------------------------------------------------------------- /primitive/build.gradle.kts: -------------------------------------------------------------------------------- 1 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 2 | 3 | val modCompileOnly: Configuration by configurations.creating { 4 | configurations.compileClasspath.get().extendsFrom(this) 5 | } 6 | val modRuntimeOnly: Configuration by configurations.creating { 7 | configurations.runtimeClasspath.get().extendsFrom(this) 8 | } 9 | val mod: Configuration by configurations.creating { 10 | configurations.compileClasspath.get().extendsFrom(this) 11 | configurations.runtimeClasspath.get().extendsFrom(this) 12 | } 13 | 14 | repositories { 15 | maven("https://maven.wispforest.io") 16 | maven("https://maven.minecraftforge.net") 17 | } 18 | 19 | unimined.minecraft { 20 | side("client") 21 | 22 | version("primitive_minecraft_version"()) 23 | 24 | runs.config("client") { 25 | javaVersion = JavaVersion.VERSION_17 26 | } 27 | 28 | fabric { 29 | loader("babric_version"()) 30 | } 31 | 32 | mappings { 33 | babricIntermediary() 34 | @Suppress("UnstableApiUsage") 35 | mapping("me.alphamode:nostalgia:${"primitive_mappings_version"()}:v2", "nostalgia") { 36 | outputs("nostalgia", true) { listOf("intermediary") } 37 | mapNamespace("named", "nostalgia") 38 | sourceNamespace("intermediary") 39 | renest() 40 | } 41 | devFallbackNamespace("intermediary") 42 | } 43 | 44 | mods { 45 | remap(modCompileOnly) 46 | remap(modRuntimeOnly) 47 | remap(mod) 48 | } 49 | } 50 | 51 | repositories { 52 | maven("https://maven.glass-launcher.net/snapshots") 53 | } 54 | 55 | dependencies { 56 | compileOnly(project(":stubs")) 57 | 58 | modCompileOnly(fabricApi.stationModule(moduleName = "station-keybindings-v0", version = "station_api_version"())) { 59 | exclude(module = "fabric-loader") 60 | exclude(group = "org.ow2.asm") 61 | } 62 | modRuntimeOnly("net.modificationstation:StationAPI:${"station_api_version"()}") { 63 | exclude(module = "cursed-fabric-loader") 64 | exclude(module = "fabric-loader") 65 | exclude(group = "org.ow2.asm") 66 | } 67 | 68 | implementation("org.slf4j:slf4j-api:${"slf4j_version"()}") 69 | implementation("org.apache.logging.log4j:log4j-slf4j18-impl:${"log4j_slf4j_version"()}") 70 | } -------------------------------------------------------------------------------- /primitive/src/main/java/dev/nolij/zume/mixin/primitive/GameRendererAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.primitive; 2 | 3 | import net.minecraft.client.renderer.GameRenderer; 4 | import net.minecraft.util.SmoothFloat; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(GameRenderer.class) 9 | public interface GameRendererAccessor { 10 | 11 | @Accessor("smoothTurnX") 12 | void setSmoothTurnX(SmoothFloat value); 13 | @Accessor("smoothTurnY") 14 | void setSmoothTurnY(SmoothFloat value); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /primitive/src/main/java/dev/nolij/zume/mixin/primitive/GameRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.primitive; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.ModifyReturnValue; 5 | import dev.nolij.zume.impl.Zume; 6 | import net.minecraft.client.renderer.GameRenderer; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(GameRenderer.class) 13 | public abstract class GameRendererMixin { 14 | 15 | @Inject(method = "render(F)V", at = @At("HEAD")) 16 | public void zume$render$HEAD(CallbackInfo ci) { 17 | Zume.renderHook(); 18 | } 19 | 20 | @ModifyReturnValue(method = "getFov", at = @At("TAIL")) 21 | public float zume$getFov$TAIL(float original) { 22 | if (Zume.isFOVHookActive()) 23 | return (float) Zume.fovHook(original); 24 | 25 | return original; 26 | } 27 | 28 | @ModifyExpressionValue(method = "render(F)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;smoothCamera:Z")) 29 | public boolean zume$updateMouse$smoothCameraEnabled(boolean original) { 30 | return Zume.cinematicCameraEnabledHook(original); 31 | } 32 | 33 | @ModifyExpressionValue(method = "render(F)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Options;sensitivity:F")) 34 | public float zume$updateMouse$mouseSensitivity(float original) { 35 | return (float) Zume.mouseSensitivityHook(original); 36 | } 37 | 38 | @ModifyExpressionValue(method = "moveCameraToPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/GameRenderer;zOff:F")) 39 | public float zume$transformCamera$thirdPersonDistance(float original) { 40 | return (float) Zume.thirdPersonCameraHook(original); 41 | } 42 | 43 | @ModifyExpressionValue(method = "moveCameraToPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/GameRenderer;oldZOff:F")) 44 | public float zume$transformCamera$lastThirdPersonDistance(float original) { 45 | return (float) Zume.thirdPersonCameraHook(original); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /primitive/src/main/java/dev/nolij/zume/mixin/primitive/MinecraftAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.primitive; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(Minecraft.class) 8 | public interface MinecraftAccessor { 9 | 10 | @Accessor("instance") 11 | static Minecraft getInstance() { 12 | return null; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /primitive/src/main/java/dev/nolij/zume/mixin/primitive/MinecraftMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.primitive; 2 | 3 | import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; 4 | import dev.nolij.zume.impl.Zume; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.world.entity.player.Inventory; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | 10 | @Mixin(value = Minecraft.class, priority = 500) 11 | public abstract class MinecraftMixin { 12 | 13 | @WrapWithCondition(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;swapPaint(I)V")) 14 | public boolean onMouseScroll$scrollInHotbar(Inventory instance, int scrollAmount) { 15 | return !Zume.mouseScrollHook(scrollAmount); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /primitive/src/main/java/dev/nolij/zume/primitive/PrimitiveZume.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.primitive; 2 | 3 | import dev.nolij.zume.impl.CameraPerspective; 4 | import dev.nolij.zume.impl.IZumeImplementation; 5 | import dev.nolij.zume.impl.Zume; 6 | import dev.nolij.zume.mixin.primitive.GameRendererAccessor; 7 | import dev.nolij.zume.mixin.primitive.MinecraftAccessor; 8 | import dev.nolij.zumegradle.proguard.ProGuardKeep; 9 | import net.fabricmc.api.ClientModInitializer; 10 | import net.fabricmc.api.EnvType; 11 | import net.fabricmc.loader.api.FabricLoader; 12 | import net.mine_diver.unsafeevents.listener.EventListener; 13 | import net.minecraft.client.KeyMapping; 14 | import net.minecraft.util.SmoothFloat; 15 | import net.modificationstation.stationapi.api.client.event.option.KeyBindingRegisterEvent; 16 | 17 | import java.util.List; 18 | 19 | public class PrimitiveZume implements ClientModInitializer, IZumeImplementation { 20 | 21 | @Override 22 | public void onInitializeClient() { 23 | if (FabricLoader.getInstance().getEnvironmentType() != EnvType.CLIENT) 24 | return; 25 | 26 | Zume.LOGGER.info("Loading Primitive Zume..."); 27 | 28 | Zume.registerImplementation(this, FabricLoader.getInstance().getConfigDir()); 29 | } 30 | 31 | @Override 32 | public boolean isZoomPressed() { 33 | //noinspection UnreachableCode,DataFlowIssue 34 | return MinecraftAccessor.getInstance().screen == null && ZumeKeyBind.ZOOM.isPressed(); 35 | } 36 | 37 | @Override 38 | public boolean isZoomInPressed() { 39 | return ZumeKeyBind.ZOOM_IN.isPressed(); 40 | } 41 | 42 | @Override 43 | public boolean isZoomOutPressed() { 44 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 45 | } 46 | 47 | @Override 48 | public CameraPerspective getCameraPerspective() { 49 | //noinspection UnreachableCode,DataFlowIssue 50 | return MinecraftAccessor.getInstance().options.thirdPersonView 51 | ? CameraPerspective.THIRD_PERSON 52 | : CameraPerspective.FIRST_PERSON; 53 | } 54 | 55 | @Override 56 | public void onZoomActivate() { 57 | //noinspection DataFlowIssue 58 | if (Zume.config.enableCinematicZoom && !MinecraftAccessor.getInstance().options.smoothCamera) { 59 | final GameRendererAccessor gameRenderer = (GameRendererAccessor) MinecraftAccessor.getInstance().gameRenderer; 60 | gameRenderer.setSmoothTurnX(new SmoothFloat()); 61 | gameRenderer.setSmoothTurnY(new SmoothFloat()); 62 | } 63 | } 64 | 65 | @ProGuardKeep.WithObfuscation 66 | @EventListener 67 | public static void registerKeyBindings(KeyBindingRegisterEvent event) { 68 | final List binds = event.keyBindings; 69 | 70 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 71 | binds.add(keyBind.value); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /primitive/src/main/java/dev/nolij/zume/primitive/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.primitive; 2 | 3 | import net.minecraft.client.KeyMapping; 4 | import org.lwjgl.input.Keyboard; 5 | 6 | public enum ZumeKeyBind { 7 | 8 | ZOOM("zume.zoom", Keyboard.KEY_Z), 9 | ZOOM_IN("zume.zoom_in", Keyboard.KEY_EQUALS), 10 | ZOOM_OUT("zume.zoom_out", Keyboard.KEY_MINUS), 11 | 12 | ; 13 | 14 | public final KeyMapping value; 15 | 16 | public boolean isPressed() { 17 | return Keyboard.isKeyDown(value.key); 18 | } 19 | 20 | ZumeKeyBind(String translationKey, int keyId) { 21 | this.value = new KeyMapping(translationKey, keyId); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /primitive/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "zume", 4 | "version": "${mod_version}", 5 | "name": "${mod_name}", 6 | "description": "${mod_description}", 7 | "authors": [ 8 | "${nolij}" 9 | ], 10 | "contact": { 11 | "website": "${mod_url}", 12 | "repo": "${repo_url}", 13 | "issues": "${issue_url}" 14 | }, 15 | "license": "OSL-3.0", 16 | "icon": "icon.png", 17 | "environment": "client", 18 | "entrypoints": { 19 | "client": [ 20 | "dev.nolij.zume.primitive.PrimitiveZume" 21 | ], 22 | "stationapi:event_bus_client": [ 23 | "dev.nolij.zume.primitive.PrimitiveZume" 24 | ] 25 | }, 26 | "mixins": [ 27 | "zume-primitive.mixins.json" 28 | ], 29 | "depends": { 30 | "fabricloader": ">=${babric_version}", 31 | "station-keybindings-v0": "*" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /primitive/src/main/resources/zume-primitive.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "primitive.GameRendererAccessor", 8 | "primitive.GameRendererMixin", 9 | "primitive.MinecraftAccessor", 10 | "primitive.MinecraftMixin" 11 | ], 12 | "injectors": { 13 | "defaultRequire": 1 14 | }, 15 | "refmap": "zume-primitive-refmap.json" 16 | } 17 | -------------------------------------------------------------------------------- /proguard.pro: -------------------------------------------------------------------------------- 1 | -ignorewarnings 2 | -dontnote 3 | -optimizationpasses 10 4 | -optimizations !class/merging/*,!method/marking/private,!method/marking/static,!*/specialization/*,!method/removal/parameter 5 | -allowaccessmodification 6 | #noinspection ShrinkerInvalidFlags 7 | -optimizeaggressively 8 | -repackageclasses zume 9 | -keepattributes Runtime*Annotations,AnnotationDefault # keep annotations 10 | 11 | -keep public class dev.nolij.zume.api.** { public *; } # public APIs 12 | -keepclassmembers class dev.nolij.zume.impl.config.ZumeConfigImpl { public ; } # dont rename config fields 13 | -keepclassmembers,allowoptimization class dev.nolij.zume.ZumeMixinPlugin { 14 | public *; 15 | } 16 | -keep @org.spongepowered.asm.mixin.Mixin class * { 17 | @org.spongepowered.asm.mixin.Overwrite *; 18 | @org.spongepowered.asm.mixin.Shadow *; 19 | } 20 | -keepclassmembers,allowobfuscation @org.spongepowered.asm.mixin.Mixin class * { *; } 21 | 22 | # Forge entrypoints 23 | -keep,allowobfuscation @*.*.fml.common.Mod class dev.nolij.zume.** { 24 | public (...); 25 | } 26 | 27 | # Platform implementations 28 | # Forge Event Subscribers 29 | -keepclasseswithmembers,allowobfuscation class dev.nolij.zume.** { 30 | @*.*.fml.common.eventhandler.SubscribeEvent ; 31 | } 32 | -keepclasseswithmembers,allowobfuscation class dev.nolij.zume.** { 33 | @*.*.fml.common.Mod$EventHandler ; 34 | } 35 | 36 | -adaptclassstrings 37 | -adaptresourcefilecontents fabric.mod.json 38 | 39 | # screens 40 | -keepclassmembers class dev.nolij.zume.** extends net.minecraft.class_437, 41 | net.minecraft.client.gui.screens.Screen, 42 | net.minecraft.client.gui.screen.Screen { 43 | !private ; 44 | } 45 | 46 | # Legacy Forge config providers 47 | -keep,allowoptimization,allowobfuscation class dev.nolij.zume.** extends *.*.client.gui.ForgeGuiFactory 48 | -keepclassmembers,allowoptimization class dev.nolij.zume.** extends *.*.fml.client.config.GuiConfig, 49 | *.*.client.gui.ForgeGuiFactory { 50 | public ; 51 | } 52 | 53 | # Fabric entrypoints 54 | -keep,allowoptimization,allowobfuscation class dev.nolij.zume.FabricZumeBootstrapper 55 | -keep,allowoptimization,allowobfuscation class dev.nolij.zume.modern.integration.modmenu.ZumeModMenuIntegration 56 | 57 | -keep @dev.nolij.zumegradle.proguard.ProGuardKeep class * { *; } 58 | -keepclassmembers class * { @dev.nolij.zumegradle.proguard.ProGuardKeep *; } 59 | 60 | -keep,allowobfuscation @dev.nolij.zumegradle.proguard.ProGuardKeep$WithObfuscation class * { *; } 61 | -keepclassmembers,allowobfuscation class * { @dev.nolij.zumegradle.proguard.ProGuardKeep$WithObfuscation *; } 62 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | plugins { 8 | id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0") 9 | } 10 | 11 | rootProject.name = "zume" 12 | 13 | include("stubs") 14 | include("api") 15 | include("modern") 16 | include("legacy") 17 | include("primitive") 18 | include("archaic") 19 | include("vintage") 20 | include("neoforge") 21 | include("lexforge") 22 | include("lexforge18") 23 | include("lexforge16") 24 | include(":integration:embeddium") -------------------------------------------------------------------------------- /src/main/java/dev/nolij/zume/FabricZumeBootstrapper.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import dev.nolij.zume.legacy.LegacyZume; 5 | import dev.nolij.zume.modern.ModernZume; 6 | import dev.nolij.zume.primitive.PrimitiveZume; 7 | import net.fabricmc.api.ClientModInitializer; 8 | import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; 9 | import net.fabricmc.loader.impl.gui.FabricGuiEntry; 10 | import net.fabricmc.loader.impl.gui.FabricStatusTree; 11 | 12 | public class FabricZumeBootstrapper implements ClientModInitializer, PreLaunchEntrypoint { 13 | 14 | private static final String MISSING_DEPENDENCY_MESSAGE = """ 15 | Failed to detect which variant of Zume to load! Ensure all dependencies are installed: 16 | Fabric (14.4+): Fabric API (fabric-key-binding-api-v1) 17 | Legacy Fabric (6.4-12.2): Legacy Fabric API (legacy-fabric-keybinding-api-v1-common) 18 | Babric (b7.3): Station API (station-keybindings-v0)"""; 19 | 20 | @Override 21 | public void onPreLaunch() { 22 | if (ZumeMixinPlugin.ZUME_VARIANT != null) 23 | return; 24 | 25 | Zume.LOGGER.error(MISSING_DEPENDENCY_MESSAGE); 26 | FabricGuiEntry.displayError("Incompatible mods found!", null, tree -> { 27 | var tab = tree.addTab("Error"); 28 | tab.node.addMessage(MISSING_DEPENDENCY_MESSAGE, FabricStatusTree.FabricTreeWarningLevel.ERROR); 29 | tree.tabs.removeIf(x -> x != tab); 30 | }, true); 31 | } 32 | 33 | @Override 34 | public void onInitializeClient() { 35 | if (ZumeMixinPlugin.ZUME_VARIANT == null) 36 | return; 37 | 38 | switch (ZumeMixinPlugin.ZUME_VARIANT) { 39 | case ZumeMixinPlugin.MODERN -> new ModernZume().onInitializeClient(); 40 | case ZumeMixinPlugin.LEGACY -> new LegacyZume().onInitializeClient(); 41 | case ZumeMixinPlugin.PRIMITIVE -> new PrimitiveZume().onInitializeClient(); 42 | } 43 | 44 | Zume.postInit(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/zume/ForgeZumeBootstrapper.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import dev.nolij.zume.lexforge.LexZume; 5 | import dev.nolij.zume.lexforge16.LexZume16; 6 | import dev.nolij.zume.lexforge18.LexZume18; 7 | import dev.nolij.zume.vintage.VintageZume; 8 | import net.minecraftforge.fml.common.Mod; 9 | 10 | import static dev.nolij.zume.impl.ZumeConstants.*; 11 | 12 | @Mod( 13 | value = MOD_ID, 14 | modid = MOD_ID, 15 | name = MOD_NAME, 16 | version = MOD_VERSION, 17 | clientSideOnly = true, 18 | acceptedMinecraftVersions = VINTAGE_VERSION_RANGE, 19 | guiFactory = "dev.nolij.zume.vintage.VintageConfigProvider") 20 | public class ForgeZumeBootstrapper { 21 | 22 | public ForgeZumeBootstrapper() { 23 | if (ZumeMixinPlugin.ZUME_VARIANT == null) 24 | throw new AssertionError(""" 25 | Mixins did not load! Zume requires Mixins in order to work properly. 26 | Please install one of the following mixin loaders: 27 | 14.4 - 16.0: MixinBootstrap 28 | 8.9 - 12.2: MixinBooter >= 5.0 29 | 7.10 - 12.2: UniMixins >= 0.1.15"""); 30 | 31 | switch (ZumeMixinPlugin.ZUME_VARIANT) { 32 | case ZumeMixinPlugin.LEXFORGE -> new LexZume(); 33 | case ZumeMixinPlugin.LEXFORGE18 -> new LexZume18(); 34 | case ZumeMixinPlugin.LEXFORGE16 -> new LexZume16(); 35 | case ZumeMixinPlugin.VINTAGE_FORGE -> new VintageZume(); 36 | } 37 | 38 | Zume.postInit(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/zume/ZumeMixinPlugin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume; 2 | 3 | import dev.nolij.libnolij.refraction.Refraction; 4 | import org.objectweb.asm.tree.ClassNode; 5 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 6 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 7 | 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public final class ZumeMixinPlugin implements IMixinConfigPlugin { 12 | 13 | static final String MODERN = "modern"; 14 | static final String PRIMITIVE = "primitive"; 15 | static final String LEGACY = "legacy"; 16 | static final String ARCHAIC_FORGE = "archaic"; 17 | static final String VINTAGE_FORGE = "vintage"; 18 | static final String LEXFORGE = "lexforge"; 19 | static final String LEXFORGE18 = "lexforge18"; 20 | static final String LEXFORGE16 = "lexforge16"; 21 | 22 | private static final ClassLoader CLASS_LOADER = ZumeMixinPlugin.class.getClassLoader(); 23 | 24 | static final String ZUME_VARIANT; 25 | private static final String implementationMixinPackage; 26 | 27 | static { 28 | String forgeVersion = null; 29 | 30 | try { 31 | //noinspection DataFlowIssue 32 | forgeVersion = (String) Refraction.safe().getMethodOrNull( 33 | Refraction.safe().getClassOrNull("net.minecraftforge.versions.forge.ForgeVersion"), 34 | "getVersion" 35 | ).invokeExact(); 36 | } catch (Throwable ignored) { } 37 | 38 | if (forgeVersion != null) { 39 | final int major = Integer.parseInt(forgeVersion.substring(0, forgeVersion.indexOf('.'))); 40 | if (major > 40) 41 | ZUME_VARIANT = LEXFORGE; 42 | else if (major > 36) 43 | ZUME_VARIANT = LEXFORGE18; 44 | else if (major > 25) 45 | ZUME_VARIANT = LEXFORGE16; 46 | else 47 | ZUME_VARIANT = null; 48 | } else { 49 | if (CLASS_LOADER.getResource("net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.class") != null) 50 | ZUME_VARIANT = MODERN; 51 | else if (CLASS_LOADER.getResource("net/legacyfabric/fabric/api/client/keybinding/v1/KeyBindingHelper.class") != null) 52 | ZUME_VARIANT = LEGACY; 53 | else if (CLASS_LOADER.getResource("net/modificationstation/stationapi/api/client/event/option/KeyBindingRegisterEvent.class") != null) 54 | ZUME_VARIANT = PRIMITIVE; 55 | else if ( 56 | CLASS_LOADER.getResource("cpw/mods/fml/client/registry/KeyBindingRegistry.class") == null && 57 | CLASS_LOADER.getResource("cpw/mods/fml/client/registry/ClientRegistry.class") != null) 58 | ZUME_VARIANT = ARCHAIC_FORGE; 59 | else if (CLASS_LOADER.getResource("net/minecraftforge/oredict/OreDictionary.class") != null) 60 | ZUME_VARIANT = VINTAGE_FORGE; 61 | else 62 | ZUME_VARIANT = null; 63 | } 64 | 65 | if (ZUME_VARIANT != null) 66 | implementationMixinPackage = "dev.nolij.zume.mixin." + ZUME_VARIANT + "."; 67 | else 68 | implementationMixinPackage = null; 69 | } 70 | 71 | @Override 72 | public void onLoad(String mixinPackage) {} 73 | 74 | @Override 75 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 76 | if (implementationMixinPackage == null) 77 | return false; 78 | 79 | return mixinClassName.startsWith(implementationMixinPackage); 80 | } 81 | 82 | @Override 83 | public String getRefMapperConfig() { 84 | return null; 85 | } 86 | 87 | @Override 88 | public void acceptTargets(Set myTargets, Set otherTargets) {} 89 | 90 | @Override 91 | public List getMixins() { 92 | return null; 93 | } 94 | 95 | @Override 96 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} 97 | 98 | @Override 99 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[1,)" 3 | license="OSL-3.0" 4 | issueTrackerURL="${issue_url}" 5 | 6 | [[mods]] 7 | modId="zume" 8 | version="${mod_version}" 9 | displayName="${mod_name}" 10 | displayURL="${mod_url}" 11 | logoFile="icon.png" 12 | authors="${nolij}" 13 | description=""" 14 | ${mod_description} 15 | """ 16 | 17 | [[dependencies.zume]] 18 | modId="minecraft" 19 | type="required" 20 | mandatory=true 21 | versionRange="${forge_minecraft_range}" 22 | ordering="NONE" 23 | side="CLIENT" 24 | 25 | [[dependencies.zume]] 26 | modId="embeddiumplus" 27 | type="optional" 28 | mandatory=false 29 | versionRange="[1.2.6,)" 30 | ordering="BEFORE" 31 | side="CLIENT" 32 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "zume", 4 | "version": "${mod_version}", 5 | "name": "${mod_name}", 6 | "description": "${mod_description}", 7 | "authors": [ 8 | "${nolij}" 9 | ], 10 | "contact": { 11 | "website": "${mod_url}", 12 | "repo": "${repo_url}", 13 | "issues": "${issue_url}" 14 | }, 15 | "license": "OSL-3.0", 16 | "icon": "icon.png", 17 | "environment": "client", 18 | "entrypoints": { 19 | "client": [ 20 | "dev.nolij.zume.FabricZumeBootstrapper" 21 | ], 22 | "preLaunch": [ 23 | "dev.nolij.zume.FabricZumeBootstrapper" 24 | ], 25 | "modmenu": [ 26 | "dev.nolij.zume.modern.integration.modmenu.ZumeModMenuIntegration" 27 | ], 28 | "stationapi:event_bus_client": [ 29 | "dev.nolij.zume.primitive.PrimitiveZume" 30 | ] 31 | }, 32 | "mixins": ["zume.mixins.json"], 33 | "depends": { 34 | "fabricloader": [">=${minimum_fabric_version}"] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "zume", 4 | "name": "${mod_name}", 5 | "description": "${mod_description}", 6 | "version": "${mod_version}", 7 | "mcversion": "${archaic_minecraft_version}", 8 | "url": "${mod_url}", 9 | "updateUrl": "", 10 | "authorList": ["${nolij}"], 11 | "credits": "", 12 | "logoFile": "icon.png", 13 | "screenshots": [], 14 | "dependencies": [] 15 | } 16 | ] -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Zume", 4 | "pack_format": 6 5 | } 6 | } -------------------------------------------------------------------------------- /stubs/build.gradle.kts: -------------------------------------------------------------------------------- 1 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") -------------------------------------------------------------------------------- /stubs/src/main/java/cpw/mods/fml/common/eventhandler/EventPriority.java: -------------------------------------------------------------------------------- 1 | package cpw.mods.fml.common.eventhandler; 2 | 3 | public enum EventPriority { 4 | HIGHEST, 5 | HIGH, 6 | NORMAL, 7 | LOW, 8 | LOWEST 9 | } 10 | -------------------------------------------------------------------------------- /stubs/src/main/java/dev/nolij/zumegradle/proguard/ProGuardKeep.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zumegradle.proguard; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.CLASS) 9 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) 10 | public @interface ProGuardKeep { 11 | 12 | @Retention(RetentionPolicy.CLASS) 13 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) 14 | @interface WithObfuscation {} 15 | 16 | } 17 | -------------------------------------------------------------------------------- /stubs/src/main/java/net/minecraftforge/eventbus/api/EventPriority.java: -------------------------------------------------------------------------------- 1 | package net.minecraftforge.eventbus.api; 2 | 3 | public enum EventPriority { 4 | HIGHEST, 5 | HIGH, 6 | NORMAL, 7 | LOW, 8 | LOWEST 9 | } 10 | -------------------------------------------------------------------------------- /stubs/src/main/java/net/minecraftforge/fml/common/Mod.java: -------------------------------------------------------------------------------- 1 | package net.minecraftforge.fml.common; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE}) 10 | public @interface Mod { 11 | 12 | // Pre/Modern Forge 13 | String value() default ""; 14 | 15 | // Vintage Forge 16 | String modid() default ""; 17 | String name() default ""; 18 | String version() default ""; 19 | boolean clientSideOnly() default false; 20 | String acceptedMinecraftVersions() default ""; 21 | String guiFactory() default ""; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /stubs/src/main/java/net/minecraftforge/fml/common/eventhandler/EventPriority.java: -------------------------------------------------------------------------------- 1 | package net.minecraftforge.fml.common.eventhandler; 2 | 3 | public enum EventPriority { 4 | HIGHEST, 5 | HIGH, 6 | NORMAL, 7 | LOW, 8 | LOWEST 9 | } 10 | -------------------------------------------------------------------------------- /vintage/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import xyz.wagyourtail.unimined.api.minecraft.task.RemapJarTask 2 | 3 | operator fun String.invoke(): String = rootProject.properties[this] as? String ?: error("Property $this not found") 4 | 5 | unimined.minecraft { 6 | version("vintage_minecraft_version"()) 7 | 8 | runs.config("client") { 9 | jvmArguments.addAll("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") 10 | } 11 | 12 | minecraftForge { 13 | loader("vintage_forge_version"()) 14 | mixinConfig("zume-${project.name}.mixins.json") 15 | } 16 | 17 | mappings { 18 | searge() 19 | mcp("stable", "vintage_mappings_version"()) 20 | } 21 | } 22 | 23 | repositories { 24 | maven("https://maven.cleanroommc.com/") 25 | } 26 | 27 | dependencies { 28 | compileOnly(project(":stubs")) 29 | 30 | "modImplementation"("zone.rong:mixinbooter:${"mixinbooter_version"()}") 31 | } -------------------------------------------------------------------------------- /vintage/src/main/java/dev/nolij/zume/mixin/vintage/EntityRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.mixin.vintage; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import dev.nolij.zume.impl.Zume; 5 | import net.minecraft.client.renderer.EntityRenderer; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 9 | 10 | @Mixin(EntityRenderer.class) 11 | public abstract class EntityRendererMixin { 12 | 13 | @ModifyExpressionValue(method = {"updateCameraAndRender", "updateRenderer"}, 14 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;smoothCamera:Z")) 15 | public boolean zume$updateMouse$smoothCameraEnabled(boolean original) { 16 | return Zume.cinematicCameraEnabledHook(original); 17 | } 18 | 19 | @ModifyExpressionValue(method = {"updateCameraAndRender", "updateRenderer"}, 20 | at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;mouseSensitivity:F")) 21 | public float zume$updateMouse$mouseSensitivity(float original) { 22 | return (float) Zume.mouseSensitivityHook(original); 23 | } 24 | 25 | @ModifyVariable(method = "orientCamera", at = @At(value = "STORE", ordinal = 0), ordinal = 3) 26 | public double zume$orientCamera$thirdPersonDistance(double original) { 27 | return Zume.thirdPersonCameraHook(original); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /vintage/src/main/java/dev/nolij/zume/vintage/VintageConfigProvider.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.vintage; 2 | 3 | import dev.nolij.zumegradle.proguard.ProGuardKeep; 4 | import net.minecraft.client.gui.GuiScreen; 5 | import net.minecraftforge.client.gui.ForgeGuiFactory; 6 | 7 | @SuppressWarnings("unused") 8 | public class VintageConfigProvider extends ForgeGuiFactory { 9 | 10 | @ProGuardKeep 11 | @Override 12 | public GuiScreen createConfigGui(GuiScreen parentScreen) { 13 | return new VintageZumeConfigGUI(parentScreen); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /vintage/src/main/java/dev/nolij/zume/vintage/VintageZume.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.vintage; 2 | 3 | import dev.nolij.libnolij.refraction.Refraction; 4 | import dev.nolij.zume.impl.CameraPerspective; 5 | import dev.nolij.zume.impl.IZumeImplementation; 6 | import dev.nolij.zume.impl.Zume; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.launchwrapper.Launch; 9 | import net.minecraftforge.client.event.EntityViewRenderEvent; 10 | import net.minecraftforge.client.event.MouseEvent; 11 | import net.minecraftforge.common.MinecraftForge; 12 | import net.minecraftforge.fml.client.registry.ClientRegistry; 13 | import net.minecraftforge.fml.common.Mod; 14 | import net.minecraftforge.fml.common.eventhandler.Event; 15 | import net.minecraftforge.fml.common.eventhandler.EventPriority; 16 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 17 | import net.minecraftforge.fml.common.gameevent.TickEvent; 18 | import net.minecraftforge.fml.relauncher.FMLLaunchHandler; 19 | 20 | import java.io.File; 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodType; 23 | 24 | import static dev.nolij.zume.impl.ZumeConstants.*; 25 | 26 | @Mod( 27 | modid = MOD_ID, 28 | name = MOD_NAME, 29 | version = MOD_VERSION, 30 | clientSideOnly = true, 31 | acceptedMinecraftVersions = VINTAGE_VERSION_RANGE, 32 | guiFactory = "dev.nolij.zume.vintage.VintageConfigProvider") 33 | public class VintageZume implements IZumeImplementation { 34 | 35 | public VintageZume() { 36 | if (!FMLLaunchHandler.side().isClient()) 37 | return; 38 | 39 | Zume.LOGGER.info("Loading Vintage Zume..."); 40 | 41 | Zume.registerImplementation(this, new File(Launch.minecraftHome, "config").toPath()); 42 | if (Zume.disabled) 43 | return; 44 | 45 | for (final ZumeKeyBind keyBind : ZumeKeyBind.values()) { 46 | ClientRegistry.registerKeyBinding(keyBind.value); 47 | } 48 | 49 | MinecraftForge.EVENT_BUS.register(this); 50 | } 51 | 52 | @Override 53 | public boolean isZoomPressed() { 54 | return Minecraft.getMinecraft().currentScreen == null && ZumeKeyBind.ZOOM.isPressed(); 55 | } 56 | 57 | @Override 58 | public boolean isZoomInPressed() { 59 | return ZumeKeyBind.ZOOM_IN.isPressed(); 60 | } 61 | 62 | @Override 63 | public boolean isZoomOutPressed() { 64 | return ZumeKeyBind.ZOOM_OUT.isPressed(); 65 | } 66 | 67 | @Override 68 | public CameraPerspective getCameraPerspective() { 69 | return CameraPerspective.values()[Minecraft.getMinecraft().gameSettings.thirdPersonView]; 70 | } 71 | 72 | @SubscribeEvent 73 | public void render(TickEvent.RenderTickEvent event) { 74 | if (event.phase == TickEvent.Phase.START) { 75 | Zume.renderHook(); 76 | } 77 | } 78 | 79 | @SubscribeEvent(priority = EventPriority.LOWEST) 80 | public void calculateFOV(EntityViewRenderEvent.FOVModifier event) { 81 | if (Zume.isFOVHookActive()) { 82 | event.setFOV((float) Zume.fovHook(event.getFOV())); 83 | } 84 | } 85 | 86 | private static final MethodHandle SET_CANCELED = Refraction.safe().getMethodOrNull( 87 | Event.class, 88 | "setCanceled", 89 | MethodType.methodType(void.class, MouseEvent.class, boolean.class), 90 | boolean.class 91 | ); 92 | private static final MethodHandle GET_DWHEEL = Refraction.firstNonNull( 93 | Refraction.safe().getMethodOrNull( 94 | MouseEvent.class, 95 | "getDwheel" 96 | ), 97 | Refraction.safe().getGetterOrNull( 98 | MouseEvent.class, 99 | "dwheel", 100 | int.class 101 | ) 102 | ); 103 | 104 | @SubscribeEvent(priority = EventPriority.HIGHEST) 105 | public void mouseEvent(MouseEvent mouseEvent) { 106 | //noinspection DataFlowIssue 107 | if (Zume.mouseScrollHook((int) GET_DWHEEL.invokeExact(mouseEvent))) { 108 | //noinspection DataFlowIssue 109 | SET_CANCELED.invokeExact(mouseEvent, true); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /vintage/src/main/java/dev/nolij/zume/vintage/VintageZumeConfigGUI.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.vintage; 2 | 3 | import dev.nolij.zume.impl.Zume; 4 | import net.minecraftforge.fml.client.config.GuiConfig; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.GuiScreen; 7 | 8 | import java.util.Collections; 9 | 10 | import static dev.nolij.zume.impl.ZumeConstants.MOD_ID; 11 | 12 | public class VintageZumeConfigGUI extends GuiConfig { 13 | 14 | public VintageZumeConfigGUI(GuiScreen parentScreen) { 15 | super(parentScreen, Collections.emptyList(), MOD_ID, false, false, "config"); 16 | 17 | Zume.openConfigFile(); 18 | } 19 | 20 | @Override 21 | public void initGui() { 22 | this.onGuiClosed(); 23 | Minecraft.getMinecraft().displayGuiScreen(parentScreen); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /vintage/src/main/java/dev/nolij/zume/vintage/ZumeKeyBind.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.zume.vintage; 2 | 3 | import net.minecraft.client.settings.KeyBinding; 4 | import org.lwjgl.input.Keyboard; 5 | 6 | public enum ZumeKeyBind { 7 | 8 | ZOOM("zume.zoom", Keyboard.KEY_Z), 9 | ZOOM_IN("zume.zoom_in", Keyboard.KEY_EQUALS), 10 | ZOOM_OUT("zume.zoom_out", Keyboard.KEY_MINUS), 11 | 12 | ; 13 | 14 | public final KeyBinding value; 15 | 16 | public boolean isPressed() { 17 | return value.isKeyDown(); 18 | } 19 | 20 | ZumeKeyBind(String translationKey, int code) { 21 | this.value = new KeyBinding(translationKey, code, "zume"); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /vintage/src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "zume", 4 | "name": "${mod_name}", 5 | "description": "${mod_description}", 6 | "version": "${mod_version}", 7 | "mcversion": "${vintage_minecraft_version}", 8 | "url": "${mod_url}", 9 | "updateUrl": "", 10 | "authorList": ["${nolij}"], 11 | "credits": "", 12 | "logoFile": "icon.png", 13 | "screenshots": [], 14 | "dependencies": [] 15 | } 16 | ] -------------------------------------------------------------------------------- /vintage/src/main/resources/zume-vintage.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.zume.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "client": [ 7 | "vintage.EntityRendererMixin" 8 | ], 9 | "injectors": { 10 | "defaultRequire": 1 11 | }, 12 | "refmap": "zume-vintage-refmap.json" 13 | } 14 | --------------------------------------------------------------------------------