├── .gitignore
├── LICENSE.md
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── me
│ └── dacubeking
│ └── clientsidenoteblocks
│ ├── client
│ ├── ClientSideNoteblocksClient.java
│ ├── ModConfig.java
│ └── ModMenu.java
│ ├── expiringmap
│ ├── SelfExpiringHashMap.java
│ └── SelfExpiringMap.java
│ ├── mixin
│ ├── ClientPlayerInteractionManagerMixin.java
│ ├── ClientWorldMixin.java
│ └── NoteblockMixin.java
│ └── mixininterfaces
│ ├── ClientWorldInterface.java
│ ├── NoteblockInterface.java
│ └── SoundHandlerInterface.java
└── resources
├── assets
└── clientsidenoteblocks
│ ├── icon.png
│ └── lang
│ └── en_us.json
├── clientsidenoteblocks.accesswidener
├── clientsidenoteblocks.mixins.json
└── fabric.mod.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific stuff
2 | .idea/
3 |
4 | *.iml
5 | *.ipr
6 | *.iws
7 |
8 | # IntelliJ
9 | out/
10 | # mpeltonen/sbt-idea plugin
11 | .idea_modules/
12 |
13 | # JIRA plugin
14 | atlassian-ide-plugin.xml
15 |
16 | # Compiled class file
17 | *.class
18 |
19 | # Log file
20 | *.log
21 |
22 | # BlueJ files
23 | *.ctxt
24 |
25 | # Package Files #
26 | *.jar
27 | *.war
28 | *.nar
29 | *.ear
30 | *.zip
31 | *.tar.gz
32 | *.rar
33 |
34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
35 | hs_err_pid*
36 |
37 | *~
38 |
39 | # temporary files which can be created if a process still has a handle open of a deleted file
40 | .fuse_hidden*
41 |
42 | # KDE directory preferences
43 | .directory
44 |
45 | # Linux trash folder which might appear on any partition or disk
46 | .Trash-*
47 |
48 | # .nfs files are created when an open file is removed but is still being accessed
49 | .nfs*
50 |
51 | # General
52 | .DS_Store
53 | .AppleDouble
54 | .LSOverride
55 |
56 | # Icon must end with two \r
57 | Icon
58 |
59 | # Thumbnails
60 | ._*
61 |
62 | # Files that might appear in the root of a volume
63 | .DocumentRevisions-V100
64 | .fseventsd
65 | .Spotlight-V100
66 | .TemporaryItems
67 | .Trashes
68 | .VolumeIcon.icns
69 | .com.apple.timemachine.donotpresent
70 |
71 | # Directories potentially created on remote AFP share
72 | .AppleDB
73 | .AppleDesktop
74 | Network Trash Folder
75 | Temporary Items
76 | .apdisk
77 |
78 | # Windows thumbnail cache files
79 | Thumbs.db
80 | Thumbs.db:encryptable
81 | ehthumbs.db
82 | ehthumbs_vista.db
83 |
84 | # Dump file
85 | *.stackdump
86 |
87 | # Folder config file
88 | [Dd]esktop.ini
89 |
90 | # Recycle Bin used on file shares
91 | $RECYCLE.BIN/
92 |
93 | # Windows Installer files
94 | *.cab
95 | *.msi
96 | *.msix
97 | *.msm
98 | *.msp
99 |
100 | # Windows shortcuts
101 | *.lnk
102 |
103 | .gradle
104 | build/
105 |
106 | # Ignore Gradle GUI config
107 | gradle-app.setting
108 |
109 | # Cache of project
110 | .gradletasknamecache
111 |
112 | **/build/
113 |
114 | # Common working directory
115 | run/
116 |
117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
118 | !gradle-wrapper.jar
119 |
120 | .idea/sonarlint/*
121 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ClientSideNoteblocks
2 | Remove lag caused by ping when playing the noteblocks in Minecraft.
3 |
4 | # Download
5 | Curseforge: https://www.curseforge.com/minecraft/mc-mods/client-side-noteblocks
6 |
7 | Modrinth: https://modrinth.com/mod/clientsidenoteblocks
8 |
9 | # About
10 | So I was trying to play a noteblock song in skywars, but playing the song with all of the lag that was caused by my ping made it too hard to play. In the past, I would just mute my sounds in-game and play. This mod will eliminate the lag by having the client play the sound. This also even helps reduce lag when playing noteblocks in singleplayer!
11 |
12 | Example (Video I made using this mod):
13 | https://youtu.be/5kv47QA2lTo
14 |
15 | To use this mod simply add the mods to your mods folder. You will also need to install the fabric API.
16 | The mod will automatically prevent unwanted sounds -- duplicate sounds from the server and block break sounds on noteblocks from playing. Once you install the mod it will immediately work and nothing needs to be configured!
17 |
18 | If you want to support me you can subscribe to my youtube channel: https://www.youtube.com/DaCubeKing
19 | If you use this mod while making a video it would be greatly appreciated if you link to this mod and/or my youtube channel in the description. :)
20 |
21 | # FAQ
22 | ## Can you make this for 1.8?
23 | No, this mod uses block data of noteblocks that are not sent to the client pre 1.13. This data also allows you to use a texture pack to display the current note of noteblock.
24 | ## What does this mod actually do?
25 | To play a noteblock normally in Minecraft (by clicking on it), the game first has to send a packet to the server that you've clicked the block. The server then has to process that packet and sees that a sound needs to be played. Once it sees that a sound needs to be played it sends a packet back to the client to tell it to play a sound. Only once the client receives this packet does it actually start playing the sound. The time for this could reach into the hundreds of ms (depending on your ping to the server) and can make it difficult to play a noteblock song as I did in the video. This mod solves the issue by cutting out the server when trying to play a noteblock. With this mod installed the client checks if you've clicked a noteblock and if you have plays the sound immediately. It also filters out the sounds that the server still sends to the client and the sounds that you get from breaking a block.
26 | ## Why isn't this mod working on X server?
27 | This mod will not work on some servers that use a protocol hack (ex. ViaVersion running on a proxy) because this mod requires the proper block data to be sent to the client. (Hypixel does work though)
28 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'fabric-loom' version '1.8-SNAPSHOT'
3 | id 'maven-publish'
4 | }
5 |
6 | version = project.mod_version
7 | group = project.maven_group
8 |
9 | repositories {
10 | // Add repositories to retrieve artifacts from in here.
11 | // You should only use this when depending on other mods because
12 | // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
13 | // See https://docs.gradle.org/current/userguide/declaring_repositories.html
14 | // for more information about repositories.
15 |
16 | maven {
17 | url "https://maven.terraformersmc.com/releases";
18 | }
19 | maven { url "https://maven.shedaniel.me/" }
20 | }
21 |
22 | configurations {
23 | modIncludeImplementation
24 |
25 | include.extendsFrom modIncludeImplementation
26 | modImplementation.extendsFrom modIncludeImplementation
27 | }
28 |
29 | dependencies {
30 | // To change the versions see the gradle.properties file
31 | minecraft "com.mojang:minecraft:${project.minecraft_version}"
32 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
33 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
34 |
35 | // Fabric API. This is technically optional, but you probably want it anyway.
36 | //modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
37 | modIncludeImplementation fabricApi.module("fabric-api-base", project.fabric_version)
38 | modIncludeImplementation fabricApi.module("fabric-events-interaction-v0", project.fabric_version)
39 | modIncludeImplementation fabricApi.module("fabric-key-binding-api-v1", project.fabric_version)
40 | modIncludeImplementation fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version)
41 | modIncludeImplementation fabricApi.module("fabric-screen-api-v1", project.fabric_version)
42 |
43 | // Uncomment the following line to enable the deprecated Fabric API modules.
44 | // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time.
45 |
46 | // modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}"
47 |
48 | modCompileOnly("com.terraformersmc:modmenu:${project.modmenu_version}") {
49 | exclude group: 'net.fabricmc.fabric-api', module: 'fabric-api'
50 | }
51 |
52 | modIncludeImplementation("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") {
53 | exclude(group: "net.fabricmc.fabric-api")
54 | }
55 | }
56 |
57 | base {
58 | archivesName = project.archives_base_name
59 | }
60 |
61 | processResources {
62 | inputs.property "version", project.version
63 |
64 | filesMatching("fabric.mod.json") {
65 | expand "version": project.version
66 | }
67 | }
68 |
69 | tasks.withType(JavaCompile).configureEach {
70 | // Minecraft 1.18 (1.18-pre2) upwards uses Java 17.
71 | it.options.release = 17
72 | }
73 |
74 | java {
75 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
76 | // if it is present.
77 | // If you remove this line, sources will not be generated.
78 | withSourcesJar()
79 |
80 | sourceCompatibility = JavaVersion.VERSION_17
81 | targetCompatibility = JavaVersion.VERSION_17
82 | }
83 |
84 | jar {
85 | from("LICENSE") {
86 | rename { "${it}_${base.archivesName.get()}" }
87 | }
88 | }
89 |
90 | // configure the maven publication
91 | publishing {
92 | publications {
93 | mavenJava(MavenPublication) {
94 | from components.java
95 | }
96 | }
97 |
98 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
99 | repositories {
100 | // Add repositories to publish to here.
101 | // Notice: This block does NOT have the same function as the block in the top level.
102 | // The repositories here will be used for publishing your artifact, not for
103 | // retrieving dependencies.
104 | }
105 | }
106 |
107 | loom {
108 | accessWidenerPath = file("src/main/resources/clientsidenoteblocks.accesswidener")
109 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Done to increase the memory available to gradle.
2 | org.gradle.jvmargs=-Xmx1G
3 | org.gradle.parallel=true
4 | # Fabric Properties
5 | # check these on https://fabricmc.net/develop
6 |
7 | minecraft_version=1.21.3
8 | yarn_mappings=1.21.3+build.2
9 | loader_version=0.16.7
10 |
11 | # Fabric API
12 | fabric_version=0.106.1+1.21.3
13 |
14 | # Mod Properties
15 | mod_version=2.10
16 | maven_group=me.dacubeking
17 | archives_base_name=clientsidenoteblocks
18 | # https://linkie.shedaniel.dev/dependencies?loader=fabric
19 | modmenu_version=12.0.0-beta.1
20 | cloth_config_version=16.0.141
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adaliea/ClientSideNoteblocks/900a08051ee871ae15be004dabd023a13b59c62e/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.10.2-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | jcenter()
4 | maven {
5 | name = 'Fabric'
6 | url = 'https://maven.fabricmc.net/'
7 | }
8 | gradlePluginPortal()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/client/ClientSideNoteblocksClient.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.client;
2 |
3 | import me.dacubeking.clientsidenoteblocks.expiringmap.SelfExpiringHashMap;
4 | import me.dacubeking.clientsidenoteblocks.mixininterfaces.ClientWorldInterface;
5 | import me.dacubeking.clientsidenoteblocks.mixininterfaces.NoteblockInterface;
6 | import me.shedaniel.autoconfig.AutoConfig;
7 | import me.shedaniel.autoconfig.serializer.GsonConfigSerializer;
8 | import net.fabricmc.api.ClientModInitializer;
9 | import net.fabricmc.api.EnvType;
10 | import net.fabricmc.api.Environment;
11 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
12 | import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
13 | import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
14 | import net.minecraft.block.BlockState;
15 | import net.minecraft.block.NoteBlock;
16 | import net.minecraft.block.enums.NoteBlockInstrument;
17 | import net.minecraft.client.MinecraftClient;
18 | import net.minecraft.client.option.KeyBinding;
19 | import net.minecraft.client.util.InputUtil;
20 | import net.minecraft.registry.entry.RegistryEntry;
21 | import net.minecraft.sound.SoundCategory;
22 | import net.minecraft.sound.SoundEvent;
23 | import net.minecraft.text.Text;
24 | import net.minecraft.util.ActionResult;
25 | import net.minecraft.util.Identifier;
26 | import net.minecraft.util.math.BlockPos;
27 | import org.lwjgl.glfw.GLFW;
28 |
29 | import java.util.concurrent.atomic.AtomicInteger;
30 | import java.util.logging.Logger;
31 |
32 | import static net.minecraft.block.NoteBlock.INSTRUMENT;
33 | import static net.minecraft.block.NoteBlock.NOTE;
34 |
35 | @Environment(EnvType.CLIENT)
36 | public class ClientSideNoteblocksClient implements ClientModInitializer {
37 |
38 | public static ModConfig config;
39 |
40 | public static final Logger LOGGER = Logger.getLogger("ClientSideNoteblocks");
41 |
42 | public static boolean isDebug() {
43 | return config.debug;
44 | }
45 |
46 | public static boolean isEnabled() {
47 | return config.enabled;
48 | }
49 |
50 | public static boolean shouldCancelStraySounds() {
51 | return config.alwaysCancelPlayedNoteblockServerSounds;
52 | }
53 |
54 |
55 | public static final Object NOTEBLOCK_SOUNDS_TO_CANCEL_LOCK = new Object();
56 | public static SelfExpiringHashMap NOTEBLOCK_SOUNDS_TO_CANCEL = new SelfExpiringHashMap<>(50000, 100);
57 |
58 | private double lastMaxTimeToServerSound = 0;
59 |
60 | @Override
61 | public void onInitializeClient() {
62 | AutoConfig.register(ModConfig.class, GsonConfigSerializer::new);
63 | config = AutoConfig.getConfigHolder(ModConfig.class).getConfig();
64 | lastMaxTimeToServerSound = config.maxTimeToServerSound;
65 | NOTEBLOCK_SOUNDS_TO_CANCEL = new SelfExpiringHashMap<>((long) (config.maxTimeToServerSound * 1000), 100);
66 |
67 |
68 | KeyBinding toggleKeybind = KeyBindingHelper.registerKeyBinding(new KeyBinding("Toggle", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_LEFT_BRACKET, "Client Side Noteblocks"));
69 |
70 | ClientTickEvents.END_CLIENT_TICK.register(client -> {
71 | while (toggleKeybind.wasPressed()) {
72 | if (client.player == null) return;
73 |
74 | config.enabled = !config.enabled;
75 | if (config.enabled) {
76 | client.player.sendMessage(Text.translatableWithFallback("text.clientsidenoteblocks.chat.enabled",
77 | "ClientSideNoteblocks is Enabled"), false);
78 |
79 | } else {
80 | client.player.sendMessage(Text.translatableWithFallback("text.clientsidenoteblocks.chat.disabled",
81 | "ClientSideNoteblocks is Disabled"), false);
82 | }
83 | }
84 | });
85 |
86 | AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> {
87 | if (config.enabled && lastMaxTimeToServerSound != config.maxTimeToServerSound) {
88 | lastMaxTimeToServerSound = config.maxTimeToServerSound;
89 | NOTEBLOCK_SOUNDS_TO_CANCEL = new SelfExpiringHashMap<>((long) (config.maxTimeToServerSound * 1000), 100);
90 | LOGGER.info("Max time to server sound changed to " + lastMaxTimeToServerSound);
91 | }
92 |
93 | if (!isEnabled()) return ActionResult.PASS;
94 | if (world.isClient && !player.isCreative() && !player.isSpectator()
95 | && world.getBlockState(pos).getBlock().getClass() == NoteBlock.class) {
96 | BlockState state = world.getBlockState(pos);
97 |
98 | if (MinecraftClient.getInstance().world != null &&
99 | (state.get(INSTRUMENT).isNotBaseBlock() || world.getBlockState(pos.up()).isAir())) {
100 | ClientWorldInterface clientWorldInterface = ((ClientWorldInterface) MinecraftClient.getInstance().world);
101 |
102 | RegistryEntry registryEntry;
103 | float f;
104 | NoteBlockInstrument instrument = state.get(INSTRUMENT);
105 | if (instrument.canBePitched()) {
106 | int i = state.get(NOTE);
107 | f = NoteBlock.getNotePitch(i);
108 | } else {
109 | f = 1.0f;
110 | }
111 |
112 | if (instrument.hasCustomSound()) {
113 | Identifier identifier = ((NoteblockInterface) state.getBlock()).clientSideNoteblocks$getCustomSoundPublic(world, pos);
114 | if (identifier == null) {
115 | return ActionResult.PASS;
116 | }
117 | registryEntry = RegistryEntry.of(SoundEvent.of(identifier));
118 | } else {
119 | registryEntry = instrument.getSound();
120 | }
121 |
122 | clientWorldInterface.bypassedPlaySound(null, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, registryEntry, SoundCategory.RECORDS, 3.0f, f, world.random.nextLong());
123 |
124 |
125 | synchronized (NOTEBLOCK_SOUNDS_TO_CANCEL_LOCK) {
126 | if (NOTEBLOCK_SOUNDS_TO_CANCEL.containsKey(pos)) {
127 | NOTEBLOCK_SOUNDS_TO_CANCEL.get(pos).addAndGet(2);
128 | } else {
129 | NOTEBLOCK_SOUNDS_TO_CANCEL.put(pos, new AtomicInteger(2));
130 | }
131 | }
132 | }
133 |
134 |
135 | }
136 | return ActionResult.PASS;
137 | });
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/client/ModConfig.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.client;
2 |
3 | import me.shedaniel.autoconfig.ConfigData;
4 | import me.shedaniel.autoconfig.annotation.Config;
5 | import me.shedaniel.autoconfig.annotation.ConfigEntry;
6 |
7 | @Config(name = "clientsidenoteblocks")
8 | public class ModConfig implements ConfigData {
9 |
10 | @ConfigEntry.Gui.Tooltip
11 | boolean debug = false;
12 |
13 | @ConfigEntry.Gui.Tooltip
14 | boolean enabled = true;
15 |
16 | @ConfigEntry.Gui.Tooltip
17 | boolean alwaysCancelPlayedNoteblockServerSounds = false;
18 |
19 | @ConfigEntry.Gui.Tooltip
20 | double maxTimeToServerSound = 5.0;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/client/ModMenu.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.client;
2 |
3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory;
4 | import com.terraformersmc.modmenu.api.ModMenuApi;
5 | import me.shedaniel.autoconfig.AutoConfig;
6 |
7 | public class ModMenu implements ModMenuApi {
8 | @Override
9 | public ConfigScreenFactory> getModConfigScreenFactory() {
10 | return parent -> AutoConfig.getConfigScreen(ModConfig.class, parent).get();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/expiringmap/SelfExpiringHashMap.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.expiringmap;/*
2 | * Copyright (c) 2019 Pierantonio Cangianiello
3 | *
4 | * MIT License
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | import me.dacubeking.clientsidenoteblocks.client.ClientSideNoteblocksClient;
26 | import org.jetbrains.annotations.NotNull;
27 |
28 | import java.util.*;
29 | import java.util.concurrent.ConcurrentHashMap;
30 | import java.util.concurrent.DelayQueue;
31 | import java.util.concurrent.Delayed;
32 | import java.util.concurrent.TimeUnit;
33 |
34 | /**
35 | * A HashMap which entries expires after the specified life time. The life-time can be defined on a per-key basis, or using a
36 | * default one, that is passed to the constructor.
37 | *
38 | * @param the Key type
39 | * @param the Value type
40 | * @author Pierantonio Cangianiello
41 | */
42 | public class SelfExpiringHashMap implements SelfExpiringMap {
43 |
44 | private final Map internalMap;
45 |
46 | private final Map> expiringKeys;
47 |
48 | /**
49 | * Holds the map keys using the given life time for expiration.
50 | */
51 | private final DelayQueue> delayQueue = new DelayQueue<>();
52 |
53 | /**
54 | * The default max life time in milliseconds.
55 | */
56 | private final long maxLifeTimeMillis;
57 |
58 | public SelfExpiringHashMap() {
59 | internalMap = new ConcurrentHashMap();
60 | expiringKeys = new WeakHashMap>();
61 | this.maxLifeTimeMillis = Long.MAX_VALUE;
62 | }
63 |
64 | public SelfExpiringHashMap(long defaultMaxLifeTimeMillis) {
65 | internalMap = new ConcurrentHashMap();
66 | expiringKeys = new WeakHashMap>();
67 | this.maxLifeTimeMillis = defaultMaxLifeTimeMillis;
68 | }
69 |
70 | public SelfExpiringHashMap(long defaultMaxLifeTimeMillis, int initialCapacity) {
71 | internalMap = new ConcurrentHashMap(initialCapacity);
72 | expiringKeys = new WeakHashMap>(initialCapacity);
73 | this.maxLifeTimeMillis = defaultMaxLifeTimeMillis;
74 | }
75 |
76 | public SelfExpiringHashMap(long defaultMaxLifeTimeMillis, int initialCapacity, float loadFactor) {
77 | internalMap = new ConcurrentHashMap(initialCapacity, loadFactor);
78 | expiringKeys = new WeakHashMap>(initialCapacity, loadFactor);
79 | this.maxLifeTimeMillis = defaultMaxLifeTimeMillis;
80 | }
81 |
82 | /**
83 | * {@inheritDoc}
84 | */
85 | @Override
86 | public int size() {
87 | cleanup();
88 | return internalMap.size();
89 | }
90 |
91 | /**
92 | * {@inheritDoc}
93 | */
94 | @Override
95 | public boolean isEmpty() {
96 | cleanup();
97 | return internalMap.isEmpty();
98 | }
99 |
100 | /**
101 | * {@inheritDoc}
102 | */
103 | @Override
104 | public boolean containsKey(Object key) {
105 | cleanup();
106 | return internalMap.containsKey(key);
107 | }
108 |
109 | /**
110 | * {@inheritDoc}
111 | */
112 | @Override
113 | public boolean containsValue(Object value) {
114 | cleanup();
115 | return internalMap.containsValue(value);
116 | }
117 |
118 | @Override
119 | public V get(Object key) {
120 | cleanup();
121 | renewKey((K) key);
122 | return internalMap.get(key);
123 | }
124 |
125 | /**
126 | * {@inheritDoc}
127 | */
128 | @Override
129 | public V put(K key, V value) {
130 | return this.put(key, value, maxLifeTimeMillis);
131 | }
132 |
133 | /**
134 | * {@inheritDoc}
135 | */
136 | @Override
137 | public V put(K key, V value, long lifeTimeMillis) {
138 | cleanup();
139 | ExpiringKey delayedKey = new ExpiringKey(key, lifeTimeMillis);
140 | ExpiringKey oldKey = expiringKeys.put(key, delayedKey);
141 | if (oldKey != null) {
142 | expireKey(oldKey);
143 | expiringKeys.put(key, delayedKey);
144 | }
145 | delayQueue.offer(delayedKey);
146 | return internalMap.put(key, value);
147 | }
148 |
149 | /**
150 | * {@inheritDoc}
151 | */
152 | @Override
153 | public V remove(Object key) {
154 | V removedValue = internalMap.remove(key);
155 | expireKey(expiringKeys.remove(key));
156 | return removedValue;
157 | }
158 |
159 | /**
160 | * Not supported.
161 | */
162 | @Override
163 | public void putAll(Map extends K, ? extends V> m) {
164 | throw new UnsupportedOperationException();
165 | }
166 |
167 | /**
168 | * {@inheritDoc}
169 | */
170 | @Override
171 | public boolean renewKey(K key) {
172 | ExpiringKey delayedKey = expiringKeys.get(key);
173 | if (delayedKey != null) {
174 | delayedKey.renew();
175 | return true;
176 | }
177 | return false;
178 | }
179 |
180 | private void expireKey(ExpiringKey delayedKey) {
181 | if (delayedKey != null) {
182 | delayedKey.expire();
183 | cleanup();
184 | }
185 | }
186 |
187 | /**
188 | * {@inheritDoc}
189 | */
190 | @Override
191 | public void clear() {
192 | delayQueue.clear();
193 | expiringKeys.clear();
194 | internalMap.clear();
195 | }
196 |
197 | /**
198 | * Not supported.
199 | */
200 | @Override
201 | public Set keySet() {
202 | //cleanup();
203 | return internalMap.keySet();
204 | }
205 |
206 | /**
207 | * Not supported.
208 | */
209 | @Override
210 | public Collection values() {
211 | throw new UnsupportedOperationException();
212 | }
213 |
214 | /**
215 | * Not supported.
216 | */
217 | @Override
218 | public Set> entrySet() {
219 | throw new UnsupportedOperationException();
220 | }
221 |
222 | private void cleanup() {
223 | var delayedKey = delayQueue.poll();
224 | while (delayedKey != null) {
225 | var value = internalMap.get(delayedKey.getKey());
226 | if (ClientSideNoteblocksClient.isDebug() && value instanceof Integer integer) {
227 | System.out.println("Removing expired key, server sound not played. Unplayed Sounds: " + integer);
228 |
229 | }
230 | internalMap.remove(delayedKey.getKey());
231 | expiringKeys.remove(delayedKey.getKey());
232 | delayedKey = delayQueue.poll();
233 | }
234 | }
235 |
236 | private class ExpiringKey implements Delayed {
237 |
238 | private long startTime = System.currentTimeMillis();
239 | private final long maxLifeTimeMillis;
240 | private final K key;
241 |
242 | public ExpiringKey(K key, long maxLifeTimeMillis) {
243 | this.maxLifeTimeMillis = maxLifeTimeMillis;
244 | this.key = key;
245 | }
246 |
247 | public K getKey() {
248 | return key;
249 | }
250 |
251 | /**
252 | * {@inheritDoc}
253 | */
254 | @Override
255 | public boolean equals(Object obj) {
256 | if (obj == null) {
257 | return false;
258 | }
259 | if (getClass() != obj.getClass()) {
260 | return false;
261 | }
262 | final ExpiringKey other = (ExpiringKey) obj;
263 | return Objects.equals(this.key, other.key);
264 | }
265 |
266 | /**
267 | * {@inheritDoc}
268 | */
269 | @Override
270 | public int hashCode() {
271 | int hash = 7;
272 | hash = 31 * hash + (this.key != null ? this.key.hashCode() : 0);
273 | return hash;
274 | }
275 |
276 | /**
277 | * {@inheritDoc}
278 | */
279 | @Override
280 | public long getDelay(TimeUnit unit) {
281 | return unit.convert(getDelayMillis(), TimeUnit.MILLISECONDS);
282 | }
283 |
284 | private long getDelayMillis() {
285 | return (startTime + maxLifeTimeMillis) - System.currentTimeMillis();
286 | }
287 |
288 | public void renew() {
289 | startTime = System.currentTimeMillis();
290 | }
291 |
292 | public void expire() {
293 | startTime = Long.MIN_VALUE;
294 | }
295 |
296 | /**
297 | * {@inheritDoc}
298 | */
299 | @Override
300 | public int compareTo(@NotNull Delayed that) {
301 | return Long.compare(this.getDelayMillis(), ((ExpiringKey) that).getDelayMillis());
302 | }
303 |
304 | }
305 | }
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/expiringmap/SelfExpiringMap.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.expiringmap;/*
2 | * Copyright (c) 2019 Pierantonio Cangianiello
3 | *
4 | * MIT License
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | import java.util.Map;
26 |
27 | /**
28 | * @param the Key type
29 | * @param the Value type
30 | * @author Pierantonio Cangianiello
31 | */
32 | public interface SelfExpiringMap extends Map {
33 |
34 | /**
35 | * Renews the specified key, setting the life time to the initial value.
36 | *
37 | * @param key
38 | * @return true if the key is found, false otherwise
39 | */
40 | boolean renewKey(K key);
41 |
42 | /**
43 | * Associates the given key to the given value in this map, with the specified life times in milliseconds.
44 | *
45 | * @param key
46 | * @param value
47 | * @param lifeTimeMillis
48 | * @return a previously associated object for the given key (if exists).
49 | */
50 | V put(K key, V value, long lifeTimeMillis);
51 |
52 | }
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/mixin/ClientPlayerInteractionManagerMixin.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.mixin;
2 |
3 | import me.dacubeking.clientsidenoteblocks.client.ClientSideNoteblocksClient;
4 | import net.minecraft.block.Blocks;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.client.network.ClientPlayNetworkHandler;
7 | import net.minecraft.client.network.ClientPlayerEntity;
8 | import net.minecraft.client.network.ClientPlayerInteractionManager;
9 | import net.minecraft.client.sound.SoundInstance;
10 | import net.minecraft.client.sound.SoundManager;
11 | import net.minecraft.util.math.BlockPos;
12 | import net.minecraft.util.math.Direction;
13 | import net.minecraft.world.World;
14 | import org.spongepowered.asm.mixin.Final;
15 | import org.spongepowered.asm.mixin.Mixin;
16 | import org.spongepowered.asm.mixin.Shadow;
17 | import org.spongepowered.asm.mixin.injection.At;
18 | import org.spongepowered.asm.mixin.injection.Redirect;
19 |
20 | import static net.minecraft.block.NoteBlock.INSTRUMENT;
21 |
22 | @Mixin(ClientPlayerInteractionManager.class)
23 | public abstract class ClientPlayerInteractionManagerMixin {
24 |
25 | @Final
26 | @Shadow
27 | private MinecraftClient client;
28 |
29 | @Shadow
30 | @Final
31 | private ClientPlayNetworkHandler networkHandler;
32 |
33 | @Redirect(method = "updateBlockBreakingProgress", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/sound/SoundManager;play(Lnet/minecraft/client/sound/SoundInstance;)V"))
34 | public void cancelBlockBreakSound(SoundManager instance, SoundInstance sound, BlockPos pos, Direction direction) {
35 | World world = this.client.world;
36 | ClientPlayerEntity player = this.client.player;
37 | if (!ClientSideNoteblocksClient.isEnabled()
38 | || world == null || player == null
39 | || player.isCreative() || player.isSpectator()
40 | || world.getBlockState(pos).getBlock() != Blocks.NOTE_BLOCK
41 | || (world.getBlockState(pos).get(INSTRUMENT).isNotBaseBlock() || !world.getBlockState(pos.up()).isAir())) {
42 | this.client.getSoundManager().play(sound);
43 | } else if (ClientSideNoteblocksClient.isDebug()) {
44 | ClientSideNoteblocksClient.LOGGER.info("Cancelled block break sound");
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/mixin/ClientWorldMixin.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.mixin;
2 |
3 | import me.dacubeking.clientsidenoteblocks.client.ClientSideNoteblocksClient;
4 | import me.dacubeking.clientsidenoteblocks.mixininterfaces.ClientWorldInterface;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.client.world.ClientWorld;
7 | import net.minecraft.entity.player.PlayerEntity;
8 | import net.minecraft.registry.DynamicRegistryManager;
9 | import net.minecraft.registry.RegistryKey;
10 | import net.minecraft.registry.entry.RegistryEntry;
11 | import net.minecraft.sound.SoundCategory;
12 | import net.minecraft.sound.SoundEvent;
13 | import net.minecraft.util.math.BlockPos;
14 | import net.minecraft.util.profiler.Profiler;
15 | import net.minecraft.world.MutableWorldProperties;
16 | import net.minecraft.world.World;
17 | import net.minecraft.world.dimension.DimensionType;
18 | import org.jetbrains.annotations.Nullable;
19 | import org.spongepowered.asm.mixin.Final;
20 | import org.spongepowered.asm.mixin.Mixin;
21 | import org.spongepowered.asm.mixin.Shadow;
22 | import org.spongepowered.asm.mixin.injection.At;
23 | import org.spongepowered.asm.mixin.injection.Inject;
24 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
25 |
26 | import java.util.concurrent.atomic.AtomicInteger;
27 | import java.util.function.Supplier;
28 |
29 | import static me.dacubeking.clientsidenoteblocks.client.ClientSideNoteblocksClient.NOTEBLOCK_SOUNDS_TO_CANCEL;
30 | import static me.dacubeking.clientsidenoteblocks.client.ClientSideNoteblocksClient.NOTEBLOCK_SOUNDS_TO_CANCEL_LOCK;
31 |
32 | @Mixin(ClientWorld.class)
33 | public abstract class ClientWorldMixin extends World implements ClientWorldInterface {
34 |
35 | @Final
36 | @Shadow
37 | private MinecraftClient client;
38 |
39 | @Shadow
40 | @Final
41 | private static double PARTICLE_Y_OFFSET;
42 |
43 |
44 | // Ignored by Mixin
45 | protected ClientWorldMixin(MutableWorldProperties properties, RegistryKey registryRef, DynamicRegistryManager registryManager, RegistryEntry dimensionEntry, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) {
46 | super(properties, registryRef, registryManager, dimensionEntry, isClient, debugWorld, seed, maxChainedNeighborUpdates);
47 | }
48 |
49 | @Inject(method = "playSound(Lnet/minecraft/entity/player/PlayerEntity;DDDLnet/minecraft/registry/entry/RegistryEntry;Lnet/minecraft/sound/SoundCategory;FFJ)V", at = @At("HEAD"), cancellable = true)
50 | public void playSound(PlayerEntity except, double x, double y, double z, RegistryEntry sound, SoundCategory category, float volume, float pitch, long seed, CallbackInfo ci) {
51 | BlockPos pos = new BlockPos((int) (x - 0.5), (int) (y - 0.5), (int) (z - 0.5));
52 |
53 | if (ClientSideNoteblocksClient.isEnabled()) {
54 | synchronized (NOTEBLOCK_SOUNDS_TO_CANCEL_LOCK) {
55 | if (NOTEBLOCK_SOUNDS_TO_CANCEL.containsKey(pos)) {
56 | AtomicInteger amount = NOTEBLOCK_SOUNDS_TO_CANCEL.get(pos);
57 | amount.getAndUpdate(i -> {
58 | if (i > 0) {
59 | if (ClientSideNoteblocksClient.isDebug()) {
60 | ClientSideNoteblocksClient.LOGGER.info("Cancelled server note block sound");
61 | }
62 | ci.cancel();
63 | return i - 1;
64 | } else {
65 | if (ClientSideNoteblocksClient.isDebug()) {
66 | ClientSideNoteblocksClient.LOGGER.info("Detected an extra server note block sound");
67 | }
68 | if (ClientSideNoteblocksClient.shouldCancelStraySounds()) {
69 | ci.cancel();
70 | }
71 | return 0;
72 | }
73 | });
74 | }
75 | }
76 | }
77 | }
78 |
79 |
80 | @Override
81 | public void bypassedPlaySound(@Nullable PlayerEntity except, double x, double y, double z, RegistryEntry sound, SoundCategory category, float volume, float pitch, long seed) {
82 | if (ClientSideNoteblocksClient.isDebug()) {
83 | ClientSideNoteblocksClient.LOGGER.info("Bypassed played sound");
84 | }
85 |
86 | this.playSound(x, y, z, (SoundEvent) sound.value(), category, volume, pitch, false, seed);
87 |
88 | }
89 |
90 | @Shadow
91 | private void playSound(double x, double y, double z, SoundEvent value, SoundCategory category, float volume, float pitch, boolean b, long seed) {
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/mixin/NoteblockMixin.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.mixin;
2 |
3 | import me.dacubeking.clientsidenoteblocks.mixininterfaces.NoteblockInterface;
4 | import net.minecraft.block.Block;
5 | import net.minecraft.block.NoteBlock;
6 | import net.minecraft.util.Identifier;
7 | import net.minecraft.util.math.BlockPos;
8 | import net.minecraft.world.World;
9 | import org.jetbrains.annotations.Nullable;
10 | import org.spongepowered.asm.mixin.Mixin;
11 | import org.spongepowered.asm.mixin.Shadow;
12 |
13 | @Mixin(NoteBlock.class)
14 | public abstract class NoteblockMixin extends Block implements NoteblockInterface {
15 | public NoteblockMixin(Settings settings) {
16 | super(settings);
17 | }
18 |
19 | @Shadow
20 | @Nullable
21 | protected abstract Identifier getCustomSound(World world, BlockPos pos);
22 |
23 |
24 | @Override
25 | public Identifier clientSideNoteblocks$getCustomSoundPublic(World world, BlockPos pos) {
26 | return getCustomSound(world, pos);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/mixininterfaces/ClientWorldInterface.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.mixininterfaces;
2 |
3 | import net.minecraft.entity.player.PlayerEntity;
4 | import net.minecraft.registry.entry.RegistryEntry;
5 | import net.minecraft.sound.SoundCategory;
6 | import net.minecraft.sound.SoundEvent;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | public interface ClientWorldInterface {
10 |
11 |
12 | void bypassedPlaySound(@Nullable PlayerEntity except, double x, double y, double z, RegistryEntry sound, SoundCategory category, float volume, float pitch, long seed);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/mixininterfaces/NoteblockInterface.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.mixininterfaces;
2 |
3 | import net.minecraft.util.Identifier;
4 | import net.minecraft.util.math.BlockPos;
5 | import net.minecraft.world.World;
6 |
7 | public interface NoteblockInterface {
8 | Identifier clientSideNoteblocks$getCustomSoundPublic(World world, BlockPos pos);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/me/dacubeking/clientsidenoteblocks/mixininterfaces/SoundHandlerInterface.java:
--------------------------------------------------------------------------------
1 | package me.dacubeking.clientsidenoteblocks.mixininterfaces;
2 |
3 | import net.minecraft.client.sound.SoundInstance;
4 |
5 | public interface SoundHandlerInterface {
6 | void bypassedPlay(SoundInstance sound);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/resources/assets/clientsidenoteblocks/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adaliea/ClientSideNoteblocks/900a08051ee871ae15be004dabd023a13b59c62e/src/main/resources/assets/clientsidenoteblocks/icon.png
--------------------------------------------------------------------------------
/src/main/resources/assets/clientsidenoteblocks/lang/en_us.json:
--------------------------------------------------------------------------------
1 | {
2 | "text.autoconfig.clientsidenoteblocks.title": "Client Side Noteblocks Config",
3 | "text.autoconfig.clientsidenoteblocks.option.debug": "Debug Mode",
4 | "text.autoconfig.clientsidenoteblocks.option.debug.@Tooltip": "Enables debug mode. This will print out debug messages in the console.",
5 | "text.autoconfig.clientsidenoteblocks.option.enabled": "Enabled",
6 | "text.autoconfig.clientsidenoteblocks.option.enabled.@Tooltip": "Enables the Mod. A Keybind can be assigned to this in the Keybind settings",
7 | "text.autoconfig.clientsidenoteblocks.option.alwaysCancelPlayedNoteblockServerSounds": "Always Cancel Played Noteblock Server Sounds",
8 | "text.autoconfig.clientsidenoteblocks.option.alwaysCancelPlayedNoteblockServerSounds.@Tooltip": "Recommended to leave this disabled.\nIf enabled, this will cancel all Noteblock sounds played by the server for blocks you've interacted with (instead of just the ones we expected to receive).\nThis will prevent you from hearing the pitch changes of a Noteblock or sounds played by other players once you've left-clicked a Noteblock.",
9 | "text.autoconfig.clientsidenoteblocks.option.maxTimeToServerSound": "The maximum time to wait for the server noteblock sound",
10 | "text.autoconfig.clientsidenoteblocks.option.maxTimeToServerSound.@Tooltip": "ClientSideNoteblocks needs to wait for the server to play the Noteblock sound to cancel it. This is the maximum time to wait for the server to play the sound before cancelling it.\nIncreasing this value if you have high ping or the server is lagging may help prevent you from hearing duplicate server Noteblock sound.",
11 | "text.clientsidenoteblocks.chat.enabled": "ClientSideNoteblocks is Enabled",
12 | "text.clientsidenoteblocks.chat.disabled": "ClientSideNoteblocks is Disabled",
13 | "text.clientsidenoteblocks.keybind.category": "ClientSideNoteblocks",
14 | "text.clientsidenoteblocks.keybind.toggle": "Toggle Mod Enabled"
15 | }
--------------------------------------------------------------------------------
/src/main/resources/clientsidenoteblocks.accesswidener:
--------------------------------------------------------------------------------
1 | accessWidener v1 named
2 |
--------------------------------------------------------------------------------
/src/main/resources/clientsidenoteblocks.mixins.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "minVersion": "0.8",
4 | "package": "me.dacubeking.clientsidenoteblocks.mixin",
5 | "compatibilityLevel": "JAVA_16",
6 | "mixins": [
7 | "ClientPlayerInteractionManagerMixin",
8 | "ClientWorldMixin",
9 | "NoteblockMixin"
10 | ],
11 | "client": [
12 | ],
13 | "injectors": {
14 | "defaultRequire": 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/resources/fabric.mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": 1,
3 | "id": "clientsidenoteblocks",
4 | "version": "${version}",
5 | "name": "ClientSideNoteblocks",
6 | "description": "Remove noteblock lag",
7 | "authors": [
8 | "DaCubeKing"
9 | ],
10 | "contact": {
11 | "homepage": "https://dacubeking.com/",
12 | "sources": "https://github.com/DaCubeKing/ClientSideNoteblocks/",
13 | "issues": "https://github.com/DaCubeKing/ClientSideNoteblocks/issues"
14 | },
15 | "license": "LGPL-3.0",
16 | "icon": "assets/clientsidenoteblocks/icon.png",
17 | "environment": "client",
18 | "entrypoints": {
19 | "client": [
20 | "me.dacubeking.clientsidenoteblocks.client.ClientSideNoteblocksClient"
21 | ],
22 | "modmenu": [
23 | "me.dacubeking.clientsidenoteblocks.client.ModMenu"
24 | ]
25 | },
26 | "mixins": [
27 | "clientsidenoteblocks.mixins.json"
28 | ],
29 | "depends": {
30 | "fabricloader": ">=0.14.21",
31 | "minecraft": "~1.21",
32 | "java": ">=17"
33 | },
34 | "suggests": {
35 | "modmenu": ">=4.1.0"
36 | },
37 | "accessWidener": "clientsidenoteblocks.accesswidener"
38 | }
39 |
--------------------------------------------------------------------------------