init: 完成

This commit is contained in:
TheWhiteDog9487
2026-09-07 10:02:18 +08:00
commit 08781de949
33 changed files with 1140 additions and 0 deletions
@@ -0,0 +1,9 @@
package xyz.thewhitedog9487.client.mixin.Compatibility.Controlling;
import net.fabricmc.loader.api.FabricLoader;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
public class MixinPlugin implements IMixinConfigPlugin {
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return FabricLoader.getInstance().isModLoaded(("controlling")); } }
@@ -0,0 +1,30 @@
package xyz.thewhitedog9487.client.mixin.Compatibility.Controlling;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.gui.components.Button;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import xyz.thewhitedog9487.client.MiscellaneousKt;
import xyz.thewhitedog9487.client.SettingsKt;
import xyz.thewhitedog9487.client.WhoProvideThisKeybindModClient;
/**
* <a href="https://modrinth.com/mod/controlling">Controlling</a>把原版的UI直接给换了,因此需要注入到修改之后的UI中
*
* @see xyz.thewhitedog9487.client.mixin.Compatibility.Controlling.MixinPlugin
*/
@Pseudo
@Mixin(targets = "com.blamejared.controlling.client.NewKeyBindsList$KeyEntry")
public abstract class NewKeyBindsListMixin {
@Shadow @Final private KeyMapping key;
@Shadow @Final private Button btnChangeKeyBinding;
@Inject(method = "refreshEntry", at = @At("TAIL"))
private void SetNewTooltipInControllingUI(final CallbackInfo ci) {
if (SettingsKt.getSettingsInstance().getShowDebugInfo()) {
WhoProvideThisKeybindModClient.INSTANCE.getClientLogger().debug("SetNewTooltipInControllingUI函数被调用,Key{}", key.getName()); }
MiscellaneousKt.SetNewTooltip(btnChangeKeyBinding, key); } }
@@ -0,0 +1,32 @@
package xyz.thewhitedog9487.client.mixin;
import xyz.thewhitedog9487.client.MiscellaneousKt;
import xyz.thewhitedog9487.client.SettingsKt;
import xyz.thewhitedog9487.client.WhoProvideThisKeybindModClient;
import xyz.thewhitedog9487.client.ModInfo;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.options.controls.KeyBindsList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* 针对原版的实现
*/
@Mixin(KeyBindsList.KeyEntry.class)
public abstract class KeyBindsListKeyEntryMixin {
@Shadow @Final private KeyMapping key;
@Shadow @Final private Button changeButton;
@Inject(method = "refreshEntry", at = @At("RETURN"))
private void SetTooltip(CallbackInfo ci) {
if (SettingsKt.getSettingsInstance().getShowDebugInfo()) {
WhoProvideThisKeybindModClient.INSTANCE.getClientLogger().debug("SetTooltip函数被调用,Key{}", key.getName()); }
MiscellaneousKt.SetNewTooltip(changeButton, key); } }
@@ -0,0 +1,44 @@
package xyz.thewhitedog9487.client.Event
import com.google.gson.GsonBuilder
import dev.isxander.yacl3.config.v2.api.ConfigClassHandler
import dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import net.minecraft.resources.Identifier
import xyz.thewhitedog9487.client.ConfigFilePath
import xyz.thewhitedog9487.client.CurrentConfigurationVersionCode
import xyz.thewhitedog9487.client.RegisterConfigFileReloadWatcher
import xyz.thewhitedog9487.client.Settings
import xyz.thewhitedog9487.client.SettingsHandler
import xyz.thewhitedog9487.client.SettingsInstance
import xyz.thewhitedog9487.client.WhoProvideThisKeybindModClient
fun RegisterClientLifecycleEvents() {
ClientLifecycleEvents.CLIENT_STARTED.register { ClientInstance ->
try {
SettingsHandler = ConfigClassHandler
.createBuilder(Settings::class.java)
.id(Identifier.fromNamespaceAndPath(WhoProvideThisKeybindModClient.ModID, "settings_confighandler"))
.serializer {
GsonConfigSerializerBuilder.create(it)
.setPath(ConfigFilePath)
.setJson5(true)
.appendGsonBuilder(GsonBuilder::setPrettyPrinting)
.build() }
.build()
WhoProvideThisKeybindModClient.ClientLogger.info("正在从${ConfigFilePath}加载设置...")
SettingsHandler!!.load()
SettingsInstance = SettingsHandler!!.instance()
if (SettingsInstance.ConfigurationVersionCode != CurrentConfigurationVersionCode) {
// 必要时手动处理升降级,现在不必要,就这么办了
SettingsInstance.ConfigurationVersionCode = CurrentConfigurationVersionCode
SettingsHandler!!.save() }
RegisterConfigFileReloadWatcher() }
catch (_: NoClassDefFoundError) {
SettingsInstance = Settings()
WhoProvideThisKeybindModClient.ClientLogger.warn("当前没有安装YetAnotherConfigLib,因此无法在调整和保持Mod配置") } }
ClientLifecycleEvents.CLIENT_STOPPING.register { ClientInstance ->
if (SettingsHandler == null) return@register
WhoProvideThisKeybindModClient.ClientLogger.info("正在保存设置到$ConfigFilePath...")
SettingsHandler!!.save() } }
@@ -0,0 +1,60 @@
package xyz.thewhitedog9487.client
import net.fabricmc.loader.api.FabricLoader
import net.fabricmc.loader.api.ModContainer
import net.fabricmc.loader.api.metadata.ModMetadata
import net.minecraft.client.KeyMapping
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.Tooltip
import net.minecraft.network.chat.Component
val VanillaMinecraftContainer: ModContainer = FabricLoader
.getInstance()
.getModContainer("minecraft")
.get()
val KeyMapping.Provider: ModInfo get() {
val KeyBindTranslationKey = name
val CandidateModIds = KeyBindTranslationKey.split(".")
if (CandidateModIds.size == 1){
WhoProvideThisKeybindModClient.ClientLogger.warn("这是个什么东西,按键绑定ID只有一个部分,ID: $KeyBindTranslationKey")
return ModInfo(null) }
for (CandidateModId in CandidateModIds) {
val ModContainer = FabricLoader.getInstance().getModContainer(CandidateModId)
if (ModContainer.isPresent) {
return ModContainer.map(::ModInfo).get() } }
return ModInfo(VanillaMinecraftContainer) }
data class ModInfo(val ModContainerInstance: ModContainer?) {
val ID: String = ModContainerInstance?.metadata?.id ?: Component.translatable("special_provider_UNKNOWN").string
val MetaData: ModMetadata? = ModContainerInstance?.metadata
val DisplayName: String = MetaData?.name ?: Component.translatable("special_provider_UNKNOWN").string
val HumanFriendlyName: Component = Component.translatableWithFallback("modmenu.nameTranslation." + (MetaData?.id ?: "unknown"), DisplayName) }
fun Button.SetNewTooltip(Key: KeyMapping) {
if (SettingsInstance.ModEnabled == false) return
val Mod = Key.Provider
val NewTooltip = tooltip
.get()
?.message
?.copy()
?.append(Component.literal("\n"))
?.append(Component.literal("\n"))
?: Component.empty()
// 第一行:Mod的人类可读名称
NewTooltip.append(Component.translatable("tooltip.provided_by",
Mod.HumanFriendlyName))
// 第二行:Mod的ID
if (SettingsInstance.ShowModID) {
NewTooltip.append(Component.literal("\n"))
.append(Component.translatable("tooltip.provider_mod_id",
Component.literal(Mod.ID))); }
// 第三行:按键自身的ID
if (SettingsInstance.ShowKeybindID) {
NewTooltip.append(Component.literal("\n"))
.append(Component.translatable("tooltip.key_id",
Component.literal(Key.name))) }
setTooltip(Tooltip.create(NewTooltip)) }
@@ -0,0 +1,49 @@
package xyz.thewhitedog9487.client
import com.terraformersmc.modmenu.api.ConfigScreenFactory
import com.terraformersmc.modmenu.api.ModMenuApi
import dev.isxander.yacl3.api.ConfigCategory
import dev.isxander.yacl3.api.Option
import dev.isxander.yacl3.api.OptionDescription
import dev.isxander.yacl3.api.YetAnotherConfigLib
import dev.isxander.yacl3.api.controller.BooleanControllerBuilder
import net.minecraft.network.chat.Component
class ModMenuEntryPoint: ModMenuApi {
override fun getModConfigScreenFactory() = ConfigScreenFactory { ParentScreen ->
YetAnotherConfigLib.createBuilder()
.title(Component.translatable("title.config"))
.category {
ConfigCategory.createBuilder()
.name(Component.translatable("config.category.general"))
.option {
Option.createBuilder<Boolean>()
.name(Component.translatable("option.ModEnabled"))
.description { OptionDescription.of(Component.translatable("option.ModEnabled.description") ) }
.binding( true, { SettingsInstance.ModEnabled }, { SettingsInstance.ModEnabled = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.option {
Option.createBuilder<Boolean>()
.name(Component.translatable("option.ShowModID"))
.description { OptionDescription.of(Component.translatable("option.ShowModID.description") ) }
.binding( false, { SettingsInstance.ShowModID }, { SettingsInstance.ShowModID = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.option {
Option.createBuilder<Boolean>()
.name(Component.translatable("option.ShowKeybindID"))
.description { OptionDescription.of(Component.translatable("option.ShowKeybindID.description") ) }
.binding( false, { SettingsInstance.ShowKeybindID }, { SettingsInstance.ShowKeybindID = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.option {
Option.createBuilder<Boolean>()
.name(Component.translatable("option.ShowDebugInfo"))
.description { OptionDescription.of(Component.translatable("option.ShowDebugInfo.description") ) }
.binding( false, { SettingsInstance.ShowDebugInfo }, { SettingsInstance.ShowDebugInfo = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.build() }
.build()
.generateScreen(ParentScreen) } }
@@ -0,0 +1,102 @@
package xyz.thewhitedog9487.client
import dev.isxander.yacl3.config.v2.api.ConfigClassHandler
import dev.isxander.yacl3.config.v2.api.SerialEntry
import kotlinx.io.IOException
import net.fabricmc.loader.api.FabricLoader
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
import java.nio.file.StandardWatchEventKinds.*
const val CurrentConfigurationVersionCode: Long = 0
class Settings {
@SerialEntry(comment =
"""
配置文件版本号
用于在配置文件结构发生变化且需要手动干预时进行兼容性处理
非必要情况下你不应该手动修改这个数据,否则可能导致本Mod的配置文件出现损坏或其他问题
类型:整数
取值范围:0 ~ Long.MAX_VALUE
当前值:0
""")
var ConfigurationVersionCode = CurrentConfigurationVersionCode
@SerialEntry(comment =
"""
是否启用本模组的功能
类型:布尔值
取值范围:true false
默认值:true
""")
var ModEnabled = true
@SerialEntry(comment =
"""
是否在悬停信息中显示键位提供者的ID
类型:布尔值
取值范围:true false
默认值:false
""")
var ShowModID = false
@SerialEntry(comment =
"""
是否在悬停信息中显示键位本身的ID
类型:布尔值
取值范围:true false
默认值:false
""")
var ShowKeybindID = false
@SerialEntry(comment =
"""
是否显示调试信息
这些信息用于出现和本Mod相关的问题时进行排查,一般情况下不需要开启
类型:布尔值
取值范围:true false
默认值:false
""")
var ShowDebugInfo = false }
var SettingsHandler: ConfigClassHandler<Settings>? = null
val ConfigFilePath = FabricLoader
.getInstance()
.configDir
.resolve(WhoProvideThisKeybindModClient.FriendlyModID + ".json5")
.toAbsolutePath()
@Volatile var SettingsInstance = Settings()
// region 配置文件变更监听器
fun RegisterConfigFileReloadWatcher() {
// 由GPT 5.4 Mini生成
Thread.startVirtualThread {
WhoProvideThisKeybindModClient.ClientLogger.info("正在为${ConfigFilePath}注册配置文件变更监听器...")
val ConfigDirectory = ConfigFilePath.parent
try {
val WatchService = FileSystems.getDefault().newWatchService()
ConfigDirectory.register(WatchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
while (!Thread.currentThread().isInterrupted) {
val WatchKey = WatchService.take()
var ShouldReload = false
for (EventInstance in WatchKey.pollEvents()) {
val EventKind = EventInstance.kind()
if (EventKind == StandardWatchEventKinds.OVERFLOW) {
continue }
val ChangedPath = EventInstance.context() as Path?
if (ChangedPath != null && ChangedPath == ConfigFilePath.fileName) {
ShouldReload = true } }
if (ShouldReload) {
SettingsHandler!!.load()
SettingsInstance = SettingsHandler!!.instance()
WhoProvideThisKeybindModClient.ClientLogger.info("检测到配置文件变更,已重新加载设置。") }
if (!WatchKey.reset()) {
break } }
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
} catch (e: IOException) {
throw RuntimeException(e) } } }
// endregion
@@ -0,0 +1,13 @@
package xyz.thewhitedog9487.client
import net.fabricmc.api.ClientModInitializer
import org.slf4j.LoggerFactory
import xyz.thewhitedog9487.client.Event.RegisterClientLifecycleEvents
object WhoProvideThisKeybindModClient : ClientModInitializer {
const val ModID: String = "whoprovidethiskeybind"
const val FriendlyModID = "WhoProvideThisKeybind"
val ClientLogger = LoggerFactory.getLogger(FriendlyModID)
override fun onInitializeClient() {
RegisterClientLifecycleEvents() } }
@@ -0,0 +1,10 @@
{
"required": false,
"package": "xyz.thewhitedog9487.client.mixin.Compatibility.Controlling",
"compatibilityLevel": "JAVA_25",
"plugin": "xyz.thewhitedog9487.client.mixin.Compatibility.Controlling.MixinPlugin",
"client": [
"NewKeyBindsListMixin" ],
"injectors": {
"defaultRequire": 0 }
}
@@ -0,0 +1,14 @@
{
"required": true,
"package": "xyz.thewhitedog9487.client.mixin",
"compatibilityLevel": "JAVA_25",
"client": [
"KeyBindsListKeyEntryMixin"
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

@@ -0,0 +1,20 @@
{
"modmenu.nameTranslation.whoprovidethiskeybind": "Who Provides This Keybind?",
"modmenu.descriptionTranslation.whoprovidethiskeybind": "Adds hover information on keybind change buttons in the keybinding settings screen to show which mod provides the keybind\nTip: If you need to use the config menu, install the YetAnotherConfigLib mod",
"link.bilibili": "Bilibili Homepage",
"link.blog": "TheWhiteDog9487's Blog",
"title.config": "Who Provides This Keybind?",
"config.category.general": "General",
"option.ModEnabled": "Enable Mod Functionality",
"option.ModEnabled.description": "Changes take effect immediately after modification and save without restarting the game",
"option.ShowModID": "Show Mod ID",
"option.ShowModID.description": "Display the Mod ID of the provider in the tooltip",
"option.ShowKeybindID": "Show Keybind ID",
"option.ShowKeybindID.description": "Display the keybind's own ID in the tooltip",
"option.ShowDebugInfo": "Show Debug Info",
"option.ShowDebugInfo.description": "This information is used to troubleshoot issues related to this mod; it generally does not need to be enabled",
"tooltip.provided_by": "Provided by: %s",
"tooltip.provider_mod_id": "Provider Mod ID: %s",
"tooltip.key_id": "Keybind ID: %s",
"special_provider_UNKNOWN": "Unknown"
}
@@ -0,0 +1,20 @@
{
"modmenu.nameTranslation.whoprovidethiskeybind": "谁在提供这个键位?",
"modmenu.descriptionTranslation.whoprovidethiskeybind": "在按键绑定设置界面的键位更改按钮上添加鼠标悬停信息,显示该按键绑定是由哪个Mod提供的\n提示:如果需要使用配置菜单,请安装YetAnotherConfigLib模组",
"link.bilibili": "哔哩哔哩主页",
"link.blog": "TheWhiteDog9487的博客",
"title.config": "谁在提供这个键位?",
"config.category.general": "通用",
"option.ModEnabled": "是否启用模组功能",
"option.ModEnabled.description": "无需重启游戏,修改并保存后立刻生效",
"option.ShowModID": "是否显示Mod ID",
"option.ShowModID.description": "在提示信息中显示提供该按键的Mod的ID",
"option.ShowKeybindID": "是否显示键位ID",
"option.ShowKeybindID.description": "在提示信息中显示该按键本身的ID",
"option.ShowDebugInfo": "是否显示调试信息",
"option.ShowDebugInfo.description": "这些信息用于出现和本Mod相关的问题时进行排查,一般情况下不需要开启",
"tooltip.provided_by": "提供者:%s",
"tooltip.provider_mod_id": "提供者ID%s",
"tooltip.key_id": "键位ID%s",
"special_provider_UNKNOWN": "未知"
}
+41
View File
@@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"id": "whoprovidethiskeybind",
"version": "${version}",
"name": "Who Provide This Keybind?",
"description": "Shows which mod provides each key binding via a tooltip on the keybinds screen.",
"authors": [
"TheWhiteDog9487" ],
"contact": {
"homepage": "https://modrinth.com/project/who-provide-this-keybind",
"sources": "https://github.com/TheWhiteDog9487/WhoProvideThisKeybind",
"issues": "https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/issues" },
"license": "WTFPL",
"icon": "assets/whoprovidethiskeybind/icon.png",
"environment": "client",
"entrypoints": {
"client": [ {
"value": "xyz.thewhitedog9487.client.WhoProvideThisKeybindModClient",
"adapter": "kotlin" } ],
"modmenu": [ "xyz.thewhitedog9487.client.ModMenuEntryPoint" ] },
"mixins": [ {
"config": "whoprovidethiskeybind.client.mixins.json",
"environment": "client" }, {
"config": "CompatibilityMixinConfig/controlling.mixins.json",
"environment": "client" } ],
"depends": {
"fabricloader": ">=0.19.5",
"minecraft": "~26.2",
"java": ">=25",
"fabric-api": ">=0.159.0",
"fabric-language-kotlin": ">=1.13.13+kotlin.2.4.10" },
"suggests": {
"modmenu": "*",
"yet_another_config_lib_v3": "*" },
"accessWidener": "whoprovidethiskeybind.classtweaker" ,
"custom": {
"modmenu": {
"links": {
"link.bilibili": "https://space.bilibili.com/401746666",
"link.blog": "https://www.thewhitedog9487.xyz"},
"update_checker": true } } }
@@ -0,0 +1,4 @@
classTweaker v1 official
accessible field net/minecraft/client/gui/components/AbstractWidget tooltip Lnet/minecraft/client/gui/components/WidgetTooltipHolder;
accessible field net/minecraft/client/gui/components/Tooltip message Lnet/minecraft/network/chat/Component;