Compare commits
No commits in common. "1_20_1" and "1_18_2" have entirely different histories.
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="AndroidProjectSystem">
|
|
||||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ClaudeCodeTabState">
|
|
||||||
<option name="tabSessions">
|
|
||||||
<map>
|
|
||||||
<entry key="0">
|
|
||||||
<value>
|
|
||||||
<TabSessionState>
|
|
||||||
<option name="provider" value="claude" />
|
|
||||||
<option name="sessionId" value="a3c423d7-ec3d-461e-8608-464d7db16154" />
|
|
||||||
<option name="cwd" value="$PROJECT_DIR$" />
|
|
||||||
<option name="model" value="claude-sonnet-4-6[1m]" />
|
|
||||||
<option name="permissionMode" value="acceptEdits" />
|
|
||||||
<option name="reasoningEffort" value="high" />
|
|
||||||
</TabSessionState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="distributionType" value="LOCAL" />
|
<option name="distributionType" value="LOCAL" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleHome" value="$PROJECT_DIR$/../../../projEnv/gradle/gradle-8.12" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|
@ -13,7 +14,6 @@
|
||||||
<option value="$PROJECT_DIR$/velocity-plugin" />
|
<option value="$PROJECT_DIR$/velocity-plugin" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveExternalAnnotations" value="true" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="JavaDocConfiguration">
|
|
||||||
<GENERAL>
|
|
||||||
<MODE>UPDATE</MODE>
|
|
||||||
<OVERRIDDEN_METHODS>false</OVERRIDDEN_METHODS>
|
|
||||||
<SPLITTED_CLASS_NAME>true</SPLITTED_CLASS_NAME>
|
|
||||||
<LEVELS>
|
|
||||||
<LEVEL>METHOD</LEVEL>
|
|
||||||
<LEVEL>TYPE</LEVEL>
|
|
||||||
<LEVEL>FIELD</LEVEL>
|
|
||||||
</LEVELS>
|
|
||||||
<VISIBILITIES>
|
|
||||||
<VISIBILITY>DEFAULT</VISIBILITY>
|
|
||||||
<VISIBILITY>PUBLIC</VISIBILITY>
|
|
||||||
<VISIBILITY>PROTECTED</VISIBILITY>
|
|
||||||
</VISIBILITIES>
|
|
||||||
</GENERAL>
|
|
||||||
<TEMPLATES>
|
|
||||||
<CLASSES>
|
|
||||||
<CLASS>
|
|
||||||
<KEY>^.*(public|protected|private)*.+interface\s+\w+.*</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* The interface ${name}.\n
|
|
||||||
<#if element.typeParameters?has_content> * \n
|
|
||||||
</#if>
|
|
||||||
<#list element.typeParameters as typeParameter>
|
|
||||||
* @param <${typeParameter.name}> the type parameter\n
|
|
||||||
</#list>
|
|
||||||
*/</VALUE>
|
|
||||||
</CLASS>
|
|
||||||
<CLASS>
|
|
||||||
<KEY>^.*(public|protected|private)*.+enum\s+\w+.*</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* The enum ${name}.\n
|
|
||||||
*/</VALUE>
|
|
||||||
</CLASS>
|
|
||||||
<CLASS>
|
|
||||||
<KEY>^.*(public|protected|private)*.+class\s+\w+.*</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* The type ${name}.\n
|
|
||||||
<#if element.typeParameters?has_content> * \n
|
|
||||||
</#if>
|
|
||||||
<#list element.typeParameters as typeParameter>
|
|
||||||
* @param <${typeParameter.name}> the type parameter\n
|
|
||||||
</#list>
|
|
||||||
*/</VALUE>
|
|
||||||
</CLASS>
|
|
||||||
<CLASS>
|
|
||||||
<KEY>.+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* The type ${name}.\n
|
|
||||||
*/</VALUE>
|
|
||||||
</CLASS>
|
|
||||||
</CLASSES>
|
|
||||||
<CONSTRUCTORS>
|
|
||||||
<CONSTRUCTOR>
|
|
||||||
<KEY>.+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* Instantiates a new ${name}.\n
|
|
||||||
<#if element.parameterList.parameters?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.parameterList.parameters as parameter>
|
|
||||||
* @param ${parameter.name} the ${paramNames[parameter.name]}\n
|
|
||||||
</#list>
|
|
||||||
<#if element.throwsList.referenceElements?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.throwsList.referenceElements as exception>
|
|
||||||
* @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
|
|
||||||
</#list>
|
|
||||||
*/</VALUE>
|
|
||||||
</CONSTRUCTOR>
|
|
||||||
</CONSTRUCTORS>
|
|
||||||
<METHODS>
|
|
||||||
<METHOD>
|
|
||||||
<KEY>^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* Gets ${partName}.\n
|
|
||||||
<#if element.typeParameters?has_content> * \n
|
|
||||||
</#if>
|
|
||||||
<#list element.typeParameters as typeParameter>
|
|
||||||
* @param <${typeParameter.name}> the type parameter\n
|
|
||||||
</#list>
|
|
||||||
<#if element.parameterList.parameters?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.parameterList.parameters as parameter>
|
|
||||||
* @param ${parameter.name} the ${paramNames[parameter.name]}\n
|
|
||||||
</#list>
|
|
||||||
<#if isNotVoid>
|
|
||||||
*\n
|
|
||||||
* @return the ${partName}\n
|
|
||||||
</#if>
|
|
||||||
<#if element.throwsList.referenceElements?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.throwsList.referenceElements as exception>
|
|
||||||
* @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
|
|
||||||
</#list>
|
|
||||||
*/</VALUE>
|
|
||||||
</METHOD>
|
|
||||||
<METHOD>
|
|
||||||
<KEY>^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* Sets ${partName}.\n
|
|
||||||
<#if element.typeParameters?has_content> * \n
|
|
||||||
</#if>
|
|
||||||
<#list element.typeParameters as typeParameter>
|
|
||||||
* @param <${typeParameter.name}> the type parameter\n
|
|
||||||
</#list>
|
|
||||||
<#if element.parameterList.parameters?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.parameterList.parameters as parameter>
|
|
||||||
* @param ${parameter.name} the ${paramNames[parameter.name]}\n
|
|
||||||
</#list>
|
|
||||||
<#if isNotVoid>
|
|
||||||
*\n
|
|
||||||
* @return the ${partName}\n
|
|
||||||
</#if>
|
|
||||||
<#if element.throwsList.referenceElements?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.throwsList.referenceElements as exception>
|
|
||||||
* @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
|
|
||||||
</#list>
|
|
||||||
*/</VALUE>
|
|
||||||
</METHOD>
|
|
||||||
<METHOD>
|
|
||||||
<KEY>^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* The entry point of application.\n
|
|
||||||
|
|
||||||
<#if element.parameterList.parameters?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
* @param ${element.parameterList.parameters[0].name} the input arguments\n
|
|
||||||
<#if element.throwsList.referenceElements?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.throwsList.referenceElements as exception>
|
|
||||||
* @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
|
|
||||||
</#list>
|
|
||||||
*/</VALUE>
|
|
||||||
</METHOD>
|
|
||||||
<METHOD>
|
|
||||||
<KEY>.+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* ${name}<#if isNotVoid> ${return}</#if>.\n
|
|
||||||
<#if element.typeParameters?has_content> * \n
|
|
||||||
</#if>
|
|
||||||
<#list element.typeParameters as typeParameter>
|
|
||||||
* @param <${typeParameter.name}> the type parameter\n
|
|
||||||
</#list>
|
|
||||||
<#if element.parameterList.parameters?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.parameterList.parameters as parameter>
|
|
||||||
* @param ${parameter.name} the ${paramNames[parameter.name]}\n
|
|
||||||
</#list>
|
|
||||||
<#if isNotVoid>
|
|
||||||
*\n
|
|
||||||
* @return the ${return}\n
|
|
||||||
</#if>
|
|
||||||
<#if element.throwsList.referenceElements?has_content>
|
|
||||||
*\n
|
|
||||||
</#if>
|
|
||||||
<#list element.throwsList.referenceElements as exception>
|
|
||||||
* @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
|
|
||||||
</#list>
|
|
||||||
*/</VALUE>
|
|
||||||
</METHOD>
|
|
||||||
</METHODS>
|
|
||||||
<FIELDS>
|
|
||||||
<FIELD>
|
|
||||||
<KEY>^.*(public|protected|private)*.+static.*(\w\s\w)+.+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
* The constant ${element.getName()}.\n
|
|
||||||
*/</VALUE>
|
|
||||||
</FIELD>
|
|
||||||
<FIELD>
|
|
||||||
<KEY>^.*(public|protected|private)*.*(\w\s\w)+.+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
<#if element.parent.isInterface()>
|
|
||||||
* The constant ${element.getName()}.\n
|
|
||||||
<#else>
|
|
||||||
* The ${name}.\n
|
|
||||||
</#if> */</VALUE>
|
|
||||||
</FIELD>
|
|
||||||
<FIELD>
|
|
||||||
<KEY>.+</KEY>
|
|
||||||
<VALUE>/**\n
|
|
||||||
<#if element.parent.isEnum()>
|
|
||||||
*${name} ${typeName}.\n
|
|
||||||
<#else>
|
|
||||||
* The ${name}.\n
|
|
||||||
</#if>*/</VALUE>
|
|
||||||
</FIELD>
|
|
||||||
</FIELDS>
|
|
||||||
</TEMPLATES>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="MaterialThemeProjectNewConfig">
|
|
||||||
<option name="metadata">
|
|
||||||
<MTProjectMetadataState>
|
|
||||||
<option name="migrated" value="true" />
|
|
||||||
<option name="pristineConfig" value="false" />
|
|
||||||
<option name="userId" value="-ef169b8:19dcecdb453:-7ffe" />
|
|
||||||
</MTProjectMetadataState>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -59,21 +59,12 @@ legacyForge {
|
||||||
}
|
}
|
||||||
client {
|
client {
|
||||||
client()
|
client()
|
||||||
systemProperty 'forge.enabledGameTestNamespaces', mod_id
|
|
||||||
}
|
}
|
||||||
data {
|
data {
|
||||||
data()
|
data()
|
||||||
programArguments.addAll '--mod', mod_id, '--all',
|
|
||||||
'--output', file('src/generated/resources/').absolutePath,
|
|
||||||
'--existing', file('src/main/resources/').absolutePath
|
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
server()
|
server()
|
||||||
systemProperty 'forge.enabledGameTestNamespaces', mod_id
|
|
||||||
}
|
|
||||||
gameTestServer {
|
|
||||||
type = "gameTestServer"
|
|
||||||
systemProperty 'forge.enabledGameTestNamespaces', mod_id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mods {
|
mods {
|
||||||
|
|
@ -106,7 +97,11 @@ dependencies {
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.24'
|
annotationProcessor 'org.projectlombok:lombok:1.18.24'
|
||||||
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
|
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
|
||||||
|
|
||||||
modCompileOnly "curse.maven:modern-ui-352491:6199942"
|
modImplementation "curse.maven:easy-villagers-400514:3887794"
|
||||||
|
modImplementation "curse.maven:xaeros-world-map-317780:6538320"
|
||||||
|
modImplementation "curse.maven:immersive-aircraft-666014:4679496"
|
||||||
|
modCompileOnly "curse.maven:modern-ui-352491:5229350"
|
||||||
|
modImplementation "curse.maven:iceberg-520110:4035917"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编译任务优化
|
// 编译任务优化
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,23 @@
|
||||||
# This is required to provide enough memory for the Minecraft decompilation process.
|
# This is required to provide enough memory for the Minecraft decompilation process.
|
||||||
org.gradle.jvmargs=-Xmx3G
|
org.gradle.jvmargs=-Xmx3G
|
||||||
org.gradle.daemon=false
|
org.gradle.daemon=false
|
||||||
neoForge.parchment.minecraftVersion=1.20.1
|
neoForge.parchment.minecraftVersion=1.18.2
|
||||||
neoForge.parchment.mappingsVersion=2023.09.03
|
neoForge.parchment.mappingsVersion=2022.11.06
|
||||||
|
|
||||||
## Environment Properties
|
## Environment Properties
|
||||||
|
|
||||||
# The Minecraft version must agree with the Forge version to get a valid artifact
|
# The Minecraft version must agree with the Forge version to get a valid artifact
|
||||||
minecraft_version=1.20.1
|
minecraft_version=1.18.2
|
||||||
# The Minecraft version range can use any release version of Minecraft as bounds.
|
# The Minecraft version range can use any release version of Minecraft as bounds.
|
||||||
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
|
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
|
||||||
# as they do not follow standard versioning conventions.
|
# as they do not follow standard versioning conventions.
|
||||||
minecraft_version_range=[1.20.1,1.21)
|
minecraft_version_range=[1.18.2,1.19)
|
||||||
# The Forge version must agree with the Minecraft version to get a valid artifact
|
# The Forge version must agree with the Minecraft version to get a valid artifact
|
||||||
forge_version=47.3.4
|
forge_version=40.3.0
|
||||||
# The Forge version range can use any version of Forge as bounds or match the loader version range
|
# The Forge version range can use any version of Forge as bounds or match the loader version range
|
||||||
forge_version_range=[47,)
|
forge_version_range=[40,)
|
||||||
# The loader version range can only use the major version of Forge/FML as bounds
|
# The loader version range can only use the major version of Forge/FML as bounds
|
||||||
loader_version_range=[47,)
|
loader_version_range=[40,)
|
||||||
# The mapping channel to use for mappings.
|
# The mapping channel to use for mappings.
|
||||||
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
|
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
|
||||||
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
|
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
|
||||||
|
|
@ -36,8 +36,8 @@ loader_version_range=[47,)
|
||||||
mapping_channel=parchment
|
mapping_channel=parchment
|
||||||
# The mapping version to query from the mapping channel.
|
# The mapping version to query from the mapping channel.
|
||||||
# This must match the format required by the mapping channel.
|
# This must match the format required by the mapping channel.
|
||||||
mapping_version=2023.09.03-1.20.1
|
mapping_version=2022.11.06-1.18.2
|
||||||
mapping_lasting_version=2023.09.03
|
mapping_lasting_version=2022.11.06
|
||||||
# imgui_version=1.89.0
|
# imgui_version=1.89.0
|
||||||
## Mod Properties
|
## Mod Properties
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ mod_name=Leisure Time Dock Mod
|
||||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||||
mod_license=MIT
|
mod_license=MIT
|
||||||
# The mod version. See https://semver.org/
|
# The mod version. See https://semver.org/
|
||||||
mod_version=1.2.0
|
mod_version=0.0.1.3
|
||||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||||
# This should match the base package used for the mod sources.
|
# This should match the base package used for the mod sources.
|
||||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||||
|
|
|
||||||
|
|
@ -1,100 +1,70 @@
|
||||||
package com.leisuretimedock.crossmod;
|
package com.leisuretimedock.crossmod;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.command.CrossModDebugCommand;
|
|
||||||
import com.leisuretimedock.crossmod.command.GotoServerCommand;
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfig;
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
|
||||||
import com.leisuretimedock.crossmod.command.PingCommand;
|
import com.leisuretimedock.crossmod.command.PingCommand;
|
||||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
||||||
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
||||||
import com.leisuretimedock.crossmod.reset.ClientResetManager;
|
import com.leisuretimedock.crossmod.reset.ClientResetManager;
|
||||||
import com.leisuretimedock.crossmod.util.ModLogger;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||||
import net.minecraftforge.event.TickEvent;
|
|
||||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||||
import net.minecraftforge.event.server.ServerStartedEvent;
|
import net.minecraftforge.event.server.ServerStartedEvent;
|
||||||
import net.minecraftforge.event.server.ServerStoppedEvent;
|
import net.minecraftforge.event.server.ServerStoppedEvent;
|
||||||
import net.minecraftforge.eventbus.api.IEventBus;
|
import net.minecraftforge.eventbus.api.IEventBus;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.IExtensionPoint;
|
import net.minecraftforge.fml.IExtensionPoint;
|
||||||
import net.minecraftforge.fml.ModList;
|
|
||||||
import net.minecraftforge.fml.ModLoadingContext;
|
import net.minecraftforge.fml.ModLoadingContext;
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import net.minecraftforge.fml.config.ModConfig;
|
|
||||||
import net.minecraftforge.fml.event.config.ModConfigEvent;
|
|
||||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
|
import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
|
||||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||||
import net.minecraftforge.network.NetworkConstants;
|
import net.minecraftforge.network.NetworkConstants;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Mod(CrossTeleportMod.MOD_ID)
|
@Mod(CrossTeleportMod.MOD_ID)
|
||||||
public class CrossTeleportMod {
|
public class CrossTeleportMod {
|
||||||
public static final String MOD_ID ="ltdcrossteleport";
|
public static final String MOD_ID ="ltdcrossteleport";
|
||||||
|
|
||||||
|
|
||||||
public CrossTeleportMod() {
|
public CrossTeleportMod() {
|
||||||
// 注册生命周期事件
|
// 注册生命周期事件
|
||||||
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||||
() -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true));
|
() -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true));
|
||||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, CrossServerConfig.SPEC, "cross-server.toml");
|
|
||||||
if(!FMLEnvironment.dist.isDedicatedServer()) modEventBus.addListener(ClientResetManager::init);
|
if(!FMLEnvironment.dist.isDedicatedServer()) modEventBus.addListener(ClientResetManager::init);
|
||||||
NetworkHandler.register();
|
NetworkHandler.register();
|
||||||
// 初始化文件日志
|
|
||||||
ModLogger.initFileLogging();
|
|
||||||
ModLogger.info("CrossMod initialized");
|
|
||||||
ModLogger.bundle("Hello, world!");
|
|
||||||
// 注册关闭钩子
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(ModLogger::closeFileLogging));
|
|
||||||
}
|
}
|
||||||
@Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
@Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
||||||
public static class CommonEvents {
|
public static class CommonEvents {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static MinecraftServer server;
|
public static MinecraftServer server;
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRegisterCommands(@NotNull RegisterCommandsEvent event) {
|
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||||
PingCommand.register(event.getDispatcher());
|
PingCommand.register(event.getDispatcher());
|
||||||
GotoServerCommand.register(event.getDispatcher());
|
|
||||||
CrossModDebugCommand.register(event.getDispatcher());
|
|
||||||
}
|
}
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onPlayerLoggedIn(PlayerEvent.@NotNull PlayerLoggedInEvent event) {
|
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
||||||
if (event.getEntity() instanceof ServerPlayer player) {
|
if (event.getEntity() instanceof ServerPlayer player) {
|
||||||
PingRequestManager.monitor(player);
|
PingRequestManager.monitor(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static int tickCounter = 0;
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onServerTick(TickEvent.@NotNull ServerTickEvent event) {
|
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||||
if (event.phase == TickEvent.Phase.END) {
|
|
||||||
tickCounter++;
|
|
||||||
if (tickCounter % 10 == 0) {
|
|
||||||
CrossServerConfigManager.INSTANCE.broadHashPacket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@SubscribeEvent
|
|
||||||
public static void onPlayerLoggedOut(PlayerEvent.@NotNull PlayerLoggedOutEvent event) {
|
|
||||||
if (event.getEntity() instanceof ServerPlayer player) {
|
if (event.getEntity() instanceof ServerPlayer player) {
|
||||||
PingRequestManager.unmonitor(player);
|
PingRequestManager.unmonitor(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onServerStart(@NotNull ServerStartedEvent event) {
|
public static void onServerStart(ServerStartedEvent event) {
|
||||||
server = event.getServer();
|
server = event.getServer();
|
||||||
}
|
}
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onServerStop(ServerStoppedEvent event) {
|
public static void onServerStop(ServerStoppedEvent event) {
|
||||||
PingRequestManager.close();
|
|
||||||
server = null;
|
server = null;
|
||||||
}
|
}
|
||||||
public static ServerPlayer getPlayerByUUID(UUID uuid) {
|
public static ServerPlayer getPlayerByUUID(UUID uuid) {
|
||||||
|
|
@ -117,59 +87,4 @@ public class CrossTeleportMod {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@Mod.EventBusSubscriber(modid = MOD_ID, value = Dist.DEDICATED_SERVER, bus = Mod.EventBusSubscriber.Bus.MOD)
|
|
||||||
public static class ServerModEvents {
|
|
||||||
/**
|
|
||||||
* On config loaded.
|
|
||||||
*
|
|
||||||
* @param event the event
|
|
||||||
*/
|
|
||||||
@SubscribeEvent
|
|
||||||
public static void onConfigLoaded(ModConfigEvent.@NotNull Loading event) {
|
|
||||||
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
|
|
||||||
CrossServerConfigManager.loading(CrossServerConfigManager.INSTANCE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On config reloaded.
|
|
||||||
*
|
|
||||||
* @param event the event
|
|
||||||
*/
|
|
||||||
@SubscribeEvent
|
|
||||||
public static void onConfigReloaded(ModConfigEvent.@NotNull Reloading event) {
|
|
||||||
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
|
|
||||||
CrossServerConfigManager.reloading(CrossServerConfigManager.INSTANCE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On config unloaded.
|
|
||||||
*
|
|
||||||
* @param event the event
|
|
||||||
*/
|
|
||||||
@SubscribeEvent
|
|
||||||
public static void onConfigUnloaded(ModConfigEvent.@NotNull Unloading event) {
|
|
||||||
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
|
|
||||||
CrossServerConfigManager.unloading(CrossServerConfigManager.INSTANCE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The type Mod info.
|
|
||||||
*/
|
|
||||||
public static class ModInfo {
|
|
||||||
/**
|
|
||||||
* The constant VERSION.
|
|
||||||
*/
|
|
||||||
public static final String VERSION;
|
|
||||||
static {
|
|
||||||
// 从 ModList 获取当前 ModContainer 的元数据
|
|
||||||
VERSION = ModList.get()
|
|
||||||
.getModContainerById(MOD_ID)
|
|
||||||
.map(c -> c.getModInfo().getVersion().toString())
|
|
||||||
.orElse("UNKNOWN");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public class ClientPingHandler {
|
||||||
private static PingRequestManager.PingStats lastStats;
|
private static PingRequestManager.PingStats lastStats;
|
||||||
private static long lastStatsUpdateTime;
|
private static long lastStatsUpdateTime;
|
||||||
|
|
||||||
public static void handlePingResults(@NotNull Map<UUID, Long> pingResults, Map<UUID, Double> averages) {
|
public static void handlePingResults(Map<UUID, Long> pingResults, Map<UUID, Double> averages) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
pingResults.forEach((uuid, ping) -> {
|
pingResults.forEach((uuid, ping) -> {
|
||||||
|
|
@ -29,7 +29,7 @@ public class ClientPingHandler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull String getPingDisplayText() {
|
public static String getPingDisplayText() {
|
||||||
Minecraft mc = Minecraft.getInstance();
|
Minecraft mc = Minecraft.getInstance();
|
||||||
if (mc.level == null) return "";
|
if (mc.level == null) return "";
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ public class ClientPingHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取要显示的统计文本
|
// 获取要显示的统计文本
|
||||||
public static @NotNull String getStatsDisplayText() {
|
public static String getStatsDisplayText() {
|
||||||
if (lastStats == null || System.currentTimeMillis() - lastStatsUpdateTime > 10000) {
|
if (lastStats == null || System.currentTimeMillis() - lastStatsUpdateTime > 10000) {
|
||||||
return "网络统计: 数据过期";
|
return "网络统计: 数据过期";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,31 @@ package com.leisuretimedock.crossmod.client;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
import com.leisuretimedock.crossmod.client.gui.CrossServerGui;
|
import com.leisuretimedock.crossmod.client.gui.CrossServerGui;
|
||||||
|
import com.leisuretimedock.crossmod.client.gui.GenericIceMessageScreen;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
import net.minecraftforge.client.ClientRegistry;
|
||||||
import net.minecraftforge.event.TickEvent;
|
import net.minecraftforge.event.TickEvent;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
||||||
public class KeyBindingHandler {
|
public class KeyBindingHandler {
|
||||||
public static final KeyMapping OPEN_GUI_KEY = new KeyMapping("ltd.mod.client.name.trans_server", GLFW.GLFW_KEY_HOME, "ltd.mod.client.key");
|
public static final KeyMapping OPEN_GUI_KEY = new KeyMapping("ltd.mod.client.name.trans_server", GLFW.GLFW_KEY_HOME, "ltd.mod.client.key");
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRegisterKeyMappingsEvent (@NotNull RegisterKeyMappingsEvent event) {
|
public static void onRegisterKey(FMLClientSetupEvent event) {
|
||||||
event.register(OPEN_GUI_KEY);
|
event.enqueueWork(() -> ClientRegistry.registerKeyBinding(OPEN_GUI_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
|
||||||
public static class KeyHandler {
|
public static class KeyHandler {
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onClientTick(TickEvent.@NotNull ClientTickEvent event) {
|
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||||
if (event.phase == TickEvent.Phase.END && OPEN_GUI_KEY.consumeClick()) {
|
if (event.phase == TickEvent.Phase.END && OPEN_GUI_KEY.consumeClick()) {
|
||||||
Minecraft.getInstance().setScreen(new CrossServerGui());
|
Minecraft.getInstance().setScreen(new CrossServerGui());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.leisuretimedock.crossmod.client;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
import com.leisuretimedock.crossmod.client.command.GotoCommand;
|
import com.leisuretimedock.crossmod.client.command.GotoCommand;
|
||||||
import com.leisuretimedock.crossmod.client.command.ServerMenuCommand;
|
|
||||||
import com.leisuretimedock.crossmod.client.overlay.CrossServerTipOverLay;
|
import com.leisuretimedock.crossmod.client.overlay.CrossServerTipOverLay;
|
||||||
import com.leisuretimedock.crossmod.client.overlay.PluginCommand;
|
import com.leisuretimedock.crossmod.client.overlay.PluginCommand;
|
||||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
||||||
|
|
@ -20,7 +19,6 @@ import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||||
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
|
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|
@ -29,7 +27,7 @@ public class PluginChannelClient {
|
||||||
private static final String HANDLER_NAME = CrossTeleportMod.MOD_ID + ":channel";
|
private static final String HANDLER_NAME = CrossTeleportMod.MOD_ID + ":channel";
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onLogin(ClientPlayerNetworkEvent.LoggingIn event) {
|
public static void onLogin(ClientPlayerNetworkEvent.LoggedInEvent event) {
|
||||||
log.debug("[CrossTeleportMod] 玩家登录事件触发");
|
log.debug("[CrossTeleportMod] 玩家登录事件触发");
|
||||||
if (ClientResetManager.isNegotiating.get())
|
if (ClientResetManager.isNegotiating.get())
|
||||||
ClientResetManager.isNegotiating.set(false);
|
ClientResetManager.isNegotiating.set(false);
|
||||||
|
|
@ -89,7 +87,7 @@ public class PluginChannelClient {
|
||||||
|
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onLogout(ClientPlayerNetworkEvent.@NotNull LoggingOut event) {
|
public static void onLogout(ClientPlayerNetworkEvent.LoggedOutEvent event) {
|
||||||
log.debug("[CrossTeleportMod] 玩家注销事件触发");
|
log.debug("[CrossTeleportMod] 玩家注销事件触发");
|
||||||
|
|
||||||
Connection connection = event.getConnection();
|
Connection connection = event.getConnection();
|
||||||
|
|
@ -112,8 +110,7 @@ public class PluginChannelClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRegisterCommand(@NotNull RegisterClientCommandsEvent event) {
|
public static void onRegisterCommand(RegisterClientCommandsEvent event) {
|
||||||
GotoCommand.register(event.getDispatcher());
|
GotoCommand.register(event.getDispatcher());
|
||||||
ServerMenuCommand.register(event.getDispatcher());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,17 @@ import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.Commands;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public class GotoCommand {
|
public class GotoCommand {
|
||||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("goto")
|
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("goto")
|
||||||
.then(Commands.argument("server", StringArgumentType.string())
|
.then(Commands.argument("server", StringArgumentType.string())
|
||||||
.executes(ctx -> {
|
.executes(ctx -> {
|
||||||
String server = StringArgumentType.getString(ctx, "server");
|
String server = StringArgumentType.getString(ctx, "server");
|
||||||
NetworkHandler.sendTeleportRequest(server);
|
NetworkHandler.sendTeleportRequest(server);
|
||||||
ctx.getSource().sendSuccess(
|
ctx.getSource().sendSuccess(
|
||||||
() -> Component.translatable("ltd.mod.client.request.goto",server), false);
|
new TranslatableComponent("ltd.mod.client.request.goto",server), false);
|
||||||
return 1;
|
return 1;
|
||||||
}));
|
}));
|
||||||
dispatcher.register(main);
|
dispatcher.register(main);
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.client.command;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.client.gui.CrossServerGui;
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|
||||||
import com.mojang.brigadier.context.CommandContext;
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
||||||
import net.minecraft.ChatFormatting;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
|
||||||
import net.minecraft.commands.Commands;
|
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.network.chat.HoverEvent;
|
|
||||||
import net.minecraft.network.chat.Style;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
public class ServerMenuCommand {
|
|
||||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
|
||||||
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("ltdmenu")
|
|
||||||
.executes(ServerMenuCommand::msgMenu)
|
|
||||||
.then(
|
|
||||||
Commands.literal("gui")
|
|
||||||
.executes(ServerMenuCommand::guiMenu)
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
Commands.literal("msg")
|
|
||||||
.executes(ServerMenuCommand::msgMenu)
|
|
||||||
);
|
|
||||||
dispatcher.register(main);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int guiMenu(CommandContext<CommandSourceStack> ctx) {
|
|
||||||
Minecraft instance = Minecraft.getInstance();
|
|
||||||
instance.setScreen(new CrossServerGui());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int msgMenu(@NotNull CommandContext<CommandSourceStack> ctx) {
|
|
||||||
Map<String, String> servers = CrossServerConfigManager.INSTANCE.getServers();
|
|
||||||
|
|
||||||
ctx.getSource().sendSuccess(
|
|
||||||
() -> Component.translatable("ltd.mod.client.menu.command.header")
|
|
||||||
.withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD),
|
|
||||||
false);
|
|
||||||
|
|
||||||
ctx.getSource().sendSuccess(() -> Component.literal(" "), false);
|
|
||||||
|
|
||||||
if (servers.isEmpty()) {
|
|
||||||
ctx.getSource().sendSuccess(
|
|
||||||
() -> Component.translatable("ltd.mod.client.menu.button.no_servers")
|
|
||||||
.withStyle(ChatFormatting.GRAY),
|
|
||||||
false);
|
|
||||||
} else {
|
|
||||||
AtomicInteger index = new AtomicInteger(1);
|
|
||||||
servers.forEach((serverId, translateKey) -> {
|
|
||||||
Component entry = Component.literal(index.getAndIncrement() + ". [")
|
|
||||||
.withStyle(ChatFormatting.WHITE)
|
|
||||||
.append(Component.translatable(translateKey)
|
|
||||||
.withStyle(ChatFormatting.GREEN))
|
|
||||||
.append(Component.literal("]")
|
|
||||||
.withStyle(ChatFormatting.WHITE))
|
|
||||||
.withStyle(Style.EMPTY
|
|
||||||
.withClickEvent(new ClickEvent(
|
|
||||||
ClickEvent.Action.RUN_COMMAND, "/cross goto " + serverId))
|
|
||||||
.withHoverEvent(new HoverEvent(
|
|
||||||
HoverEvent.Action.SHOW_TEXT,
|
|
||||||
Component.translatable("ltd.mod.client.menu.command.hover",
|
|
||||||
Component.translatable(translateKey)))));
|
|
||||||
|
|
||||||
ctx.getSource().sendSuccess(() -> entry, false);
|
|
||||||
ctx.getSource().sendSuccess(() -> Component.literal(" "), false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +1,56 @@
|
||||||
package com.leisuretimedock.crossmod.client.gui;
|
package com.leisuretimedock.crossmod.client.gui;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
|
||||||
import com.leisuretimedock.crossmod.client.overlay.CrossServerTipOverLay;
|
import com.leisuretimedock.crossmod.client.overlay.CrossServerTipOverLay;
|
||||||
import com.leisuretimedock.crossmod.client.overlay.PingOverlayManager;
|
import com.leisuretimedock.crossmod.client.overlay.PingOverlayManager;
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.components.Checkbox;
|
import net.minecraft.client.gui.components.Checkbox;
|
||||||
import net.minecraft.client.gui.components.EditBox;
|
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.renderer.GameRenderer;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@OnlyIn(Dist.CLIENT)
|
@OnlyIn(Dist.CLIENT)
|
||||||
public class CrossServerGui extends Screen {
|
public class CrossServerGui extends Screen {
|
||||||
public final static Component TITLE = Component.translatable("ltd.mod.client.menu");
|
|
||||||
private static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport");
|
private static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport");
|
||||||
private static final ResourceLocation LOGO_TEXTURE = new ResourceLocation(CrossTeleportMod.MOD_ID, "textures/ltd_logo.png");
|
private static final ResourceLocation LOGO_TEXTURE = new ResourceLocation(CrossTeleportMod.MOD_ID, "textures/ltd_logo.png");
|
||||||
|
|
||||||
// 开发模式标志
|
|
||||||
private static final boolean DEV_MODE = !FMLEnvironment.production;
|
|
||||||
|
|
||||||
// 存储组件引用,以便在渲染时控制顺序
|
|
||||||
private Checkbox enableCrCheckBox;
|
|
||||||
private Checkbox enablePiCheckBox;
|
|
||||||
private Button closeButton;
|
|
||||||
private Button devTestButton;
|
|
||||||
private Button devToggleDebugButton;
|
|
||||||
private ServerSelectionList serverList;
|
|
||||||
private EditBox searchBox;
|
|
||||||
|
|
||||||
// 开发测试数据
|
|
||||||
private long lastUpdateTime = 0;
|
|
||||||
private int serverCount = 0;
|
|
||||||
private boolean showDevInfo = true;
|
|
||||||
|
|
||||||
public CrossServerGui() {
|
public CrossServerGui() {
|
||||||
super(TITLE);
|
super(new TranslatableComponent("ltd.mod.client.menu"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
// 先添加搜索框
|
|
||||||
initSearchBox();
|
|
||||||
|
|
||||||
// 再添加按钮和复选框(它们应该渲染在列表上方)
|
|
||||||
initButtons();
|
|
||||||
|
|
||||||
// 开发模式按钮
|
|
||||||
if (DEV_MODE) {
|
|
||||||
initDevButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后添加列表(它应该渲染在下方)
|
|
||||||
initServerList();
|
|
||||||
|
|
||||||
// 更新服务器计数
|
|
||||||
serverCount = CrossServerConfigManager.INSTANCE.getServers().size();
|
|
||||||
lastUpdateTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
// 设置初始焦点
|
|
||||||
this.setInitialFocus(searchBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initSearchBox() {
|
|
||||||
int centerX = width / 2;
|
int centerX = width / 2;
|
||||||
|
int centerY = height / 2;
|
||||||
|
int buttonWidth = 150;
|
||||||
|
int buttonHeight = 20;
|
||||||
|
int spacing = 5;
|
||||||
|
|
||||||
// 创建搜索框
|
addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY - buttonHeight - spacing,
|
||||||
searchBox = new EditBox(
|
buttonWidth, buttonHeight, new TranslatableComponent("ltd.mod.client.menu.button.1"), btn -> {
|
||||||
this.font,
|
sendCustomPayload("connect:lobby");
|
||||||
centerX - 100, // x位置
|
onClose();
|
||||||
50, // y位置(标题下方)
|
}));
|
||||||
200, // 宽度
|
|
||||||
16, // 高度
|
|
||||||
Component.translatable("ltd.mod.client.menu.search")
|
|
||||||
);
|
|
||||||
searchBox.setMaxLength(50);
|
|
||||||
searchBox.setVisible(true);
|
|
||||||
searchBox.setFocused(false);
|
|
||||||
searchBox.setHint(Component.translatable("ltd.mod.client.menu.search"));
|
|
||||||
// 启用文本框的输入
|
|
||||||
searchBox.setCanLoseFocus(true);
|
|
||||||
|
|
||||||
// 设置文本变化监听器
|
addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY,
|
||||||
searchBox.setResponder(searchText -> {
|
buttonWidth, buttonHeight, new TranslatableComponent("ltd.mod.client.menu.button.2"), btn -> {
|
||||||
if (serverList != null) {
|
sendCustomPayload("connect:survival");
|
||||||
serverList.setSearchFilter(searchText);
|
onClose();
|
||||||
}
|
}));
|
||||||
});
|
// 添加 Checkbox 控件
|
||||||
|
Checkbox enableCrCheckBox = new Checkbox(centerX - buttonWidth / 2, centerY + buttonHeight + spacing + 5,
|
||||||
addRenderableWidget(searchBox);
|
150, 20, new TranslatableComponent("ltd.mod.client.menu.checkbox.show_trans_tip"), !CrossServerTipOverLay.isShowOverlay()) {
|
||||||
}
|
|
||||||
|
|
||||||
private void initButtons() {
|
|
||||||
int centerX = width / 2;
|
|
||||||
int bottomY = height - 60; // 从底部向上60像素
|
|
||||||
|
|
||||||
// 添加 Checkbox 控件 - 显示传送提示
|
|
||||||
enableCrCheckBox = new Checkbox(centerX - 150, bottomY,
|
|
||||||
140, 20, Component.translatable("ltd.mod.client.menu.checkbox.show_trans_tip"),
|
|
||||||
!CrossServerTipOverLay.isShowOverlay()) {
|
|
||||||
@Override
|
@Override
|
||||||
public void onPress() {
|
public void onPress() {
|
||||||
super.onPress();
|
super.onPress();
|
||||||
|
|
@ -118,11 +58,9 @@ public class CrossServerGui extends Screen {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
addRenderableWidget(enableCrCheckBox);
|
addRenderableWidget(enableCrCheckBox);
|
||||||
|
// 添加 Checkbox 控件
|
||||||
// 添加 Checkbox 控件 - 显示ping统计
|
Checkbox enablePiCheckBox = new Checkbox(centerX - buttonWidth / 2, centerY + buttonHeight + spacing + 25,
|
||||||
enablePiCheckBox = new Checkbox(centerX + 10, bottomY,
|
150, 20, new TranslatableComponent("ltd.mod.client.menu.checkbox.show_ping_stat"), !PingOverlayManager.isShowOverlay()) {
|
||||||
140, 20, Component.translatable("ltd.mod.client.menu.checkbox.show_ping_stat"),
|
|
||||||
!PingOverlayManager.isShowOverlay()) {
|
|
||||||
@Override
|
@Override
|
||||||
public void onPress() {
|
public void onPress() {
|
||||||
super.onPress();
|
super.onPress();
|
||||||
|
|
@ -130,335 +68,9 @@ public class CrossServerGui extends Screen {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
addRenderableWidget(enablePiCheckBox);
|
addRenderableWidget(enablePiCheckBox);
|
||||||
|
|
||||||
// 添加关闭按钮
|
|
||||||
closeButton = Button.builder(
|
|
||||||
Component.translatable("gui.done"),
|
|
||||||
button -> this.onClose()
|
|
||||||
)
|
|
||||||
.bounds(centerX - 50, bottomY + 30, 100, 20)
|
|
||||||
.build();
|
|
||||||
addRenderableWidget(closeButton);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDevButtons() {
|
private void sendCustomPayload(String message) {
|
||||||
int centerX = width / 2;
|
|
||||||
int topY = 45;
|
|
||||||
|
|
||||||
// 开发测试按钮 - 重新加载配置
|
|
||||||
devTestButton = Button.builder(
|
|
||||||
Component.literal("§6[Dev] Reload"),
|
|
||||||
button -> {
|
|
||||||
CrossServerConfigManager.INSTANCE.reloadAll();
|
|
||||||
refreshServerList();
|
|
||||||
serverCount = CrossServerConfigManager.INSTANCE.getServers().size();
|
|
||||||
lastUpdateTime = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.bounds(centerX + 120, topY, 70, 16)
|
|
||||||
.build();
|
|
||||||
addRenderableWidget(devTestButton);
|
|
||||||
|
|
||||||
// 开发测试按钮 - 切换调试信息显示
|
|
||||||
devToggleDebugButton = Button.builder(
|
|
||||||
Component.literal("§7[Dev] " + (showDevInfo ? "Hide" : "Show") + " Debug"),
|
|
||||||
button -> {
|
|
||||||
showDevInfo = !showDevInfo;
|
|
||||||
button.setMessage(Component.literal("§7[Dev] " + (showDevInfo ? "Hide" : "Show") + " Debug"));
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.bounds(centerX + 195, topY, 80, 16)
|
|
||||||
.build();
|
|
||||||
addRenderableWidget(devToggleDebugButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initServerList() {
|
|
||||||
int screenWidth = this.width;
|
|
||||||
int screenHeight = this.height;
|
|
||||||
|
|
||||||
// 创建服务器列表,调整位置为搜索框下方
|
|
||||||
serverList = new ServerSelectionList(
|
|
||||||
this,
|
|
||||||
Minecraft.getInstance(),
|
|
||||||
screenWidth,
|
|
||||||
screenHeight,
|
|
||||||
70, // Y起始位置(搜索框下方)
|
|
||||||
height - 64, // Y结束位置
|
|
||||||
36, // 条目高度
|
|
||||||
CrossServerConfigManager.INSTANCE.getServers()
|
|
||||||
);
|
|
||||||
|
|
||||||
// 设置列表属性
|
|
||||||
serverList.setRenderBackground(false);
|
|
||||||
serverList.setRenderTopAndBottom(true);
|
|
||||||
|
|
||||||
addRenderableWidget(serverList);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新服务器列表
|
|
||||||
*/
|
|
||||||
private void refreshServerList() {
|
|
||||||
if (serverList != null) {
|
|
||||||
// 移除旧的列表
|
|
||||||
this.removeWidget(serverList);
|
|
||||||
// 创建新的列表
|
|
||||||
initServerList();
|
|
||||||
// 如果有搜索文本,重新应用
|
|
||||||
if (searchBox != null && !searchBox.getValue().isEmpty()) {
|
|
||||||
serverList.setSearchFilter(searchBox.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) {
|
|
||||||
|
|
||||||
// 渲染标题
|
|
||||||
guiGraphics.drawString(this.font, this.title.getString(), this.width / 2 - font.width(this.title.getString()) / 2, 20, 0xFFFFFF);
|
|
||||||
|
|
||||||
// 重要:先渲染列表(在底层)
|
|
||||||
if (serverList != null) {
|
|
||||||
serverList.render(guiGraphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染搜索框
|
|
||||||
if (searchBox != null) {
|
|
||||||
searchBox.render(guiGraphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 然后渲染按钮和复选框(在上层)
|
|
||||||
if (enableCrCheckBox != null) {
|
|
||||||
enableCrCheckBox.render(guiGraphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
|
||||||
if (enablePiCheckBox != null) {
|
|
||||||
enablePiCheckBox.render(guiGraphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
|
||||||
if (closeButton != null) {
|
|
||||||
closeButton.render(guiGraphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染开发按钮
|
|
||||||
if (DEV_MODE) {
|
|
||||||
if (devTestButton != null) {
|
|
||||||
devTestButton.render(guiGraphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
|
||||||
if (devToggleDebugButton != null) {
|
|
||||||
devToggleDebugButton.render(guiGraphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLogo(guiGraphics);
|
|
||||||
|
|
||||||
// 渲染开发调试信息(不省略任何信息)
|
|
||||||
if (DEV_MODE && showDevInfo) {
|
|
||||||
renderDevInfo(guiGraphics);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染开发调试信息 - 完整显示所有信息
|
|
||||||
*/
|
|
||||||
private void renderDevInfo(@NotNull GuiGraphics guiGraphics) {
|
|
||||||
int x = 10;
|
|
||||||
int y = 10;
|
|
||||||
int lineHeight = 11;
|
|
||||||
int color = 0x55FF55; // 绿色
|
|
||||||
int textColor = 0xFFFFFF; // 白色
|
|
||||||
|
|
||||||
// 显示基本信息
|
|
||||||
guiGraphics.drawString(this.font, "§a[Dev Mode] §7- Debug Information", x, y, color);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
guiGraphics.drawString(this.font, "§7Servers: §f" + serverCount, x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
guiGraphics.drawString(this.font, "§7Players Online: §f" + getOnlinePlayerCount(), x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
guiGraphics.drawString(this.font, "§7Current Server: §f" + getCurrentServerName(), x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
// 显示服务器列表详细信息 - 完整显示所有服务器
|
|
||||||
Map<String, String> servers = CrossServerConfigManager.INSTANCE.getServers();
|
|
||||||
if (!servers.isEmpty()) {
|
|
||||||
guiGraphics.drawString(this.font, "§7Server List (§f" + servers.size() + "§7):", x, y, color);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
// 显示所有服务器,不省略
|
|
||||||
int count = 0;
|
|
||||||
for (Map.Entry<String, String> entry : servers.entrySet()) {
|
|
||||||
String display = "§8 " + (count + 1) + ". §f" + entry.getKey() + " §7→ §e" + entry.getValue();
|
|
||||||
// 如果文本太长,截断但保留完整信息
|
|
||||||
if (display.length() > 256) {
|
|
||||||
display = display.substring(0, 47) + "...";
|
|
||||||
}
|
|
||||||
guiGraphics.drawString(this.font, display, x + 5, y, 0xAAAAAA);
|
|
||||||
y += lineHeight;
|
|
||||||
count++;
|
|
||||||
|
|
||||||
// 防止信息超出屏幕
|
|
||||||
if (y > height - 100) {
|
|
||||||
guiGraphics.drawString(this.font, "§8... and more (scroll to see all)", x + 5, y, 0x888888);
|
|
||||||
y += lineHeight;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示内存信息
|
|
||||||
Runtime runtime = Runtime.getRuntime();
|
|
||||||
long totalMemory = runtime.totalMemory() / 1024 / 1024;
|
|
||||||
long freeMemory = runtime.freeMemory() / 1024 / 1024;
|
|
||||||
long usedMemory = totalMemory - freeMemory;
|
|
||||||
long maxMemory = runtime.maxMemory() / 1024 / 1024;
|
|
||||||
guiGraphics.drawString(this.font, "§7Memory: §f" + usedMemory + "MB §7used / §f" + totalMemory + "MB §7allocated / §f" + maxMemory + "MB §7max", x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
// 显示FPS信息
|
|
||||||
try {
|
|
||||||
int fps = Minecraft.getInstance().getFps();
|
|
||||||
String fpsColor = fps >= 60 ? "§a" : (fps >= 30 ? "§e" : "§c");
|
|
||||||
guiGraphics.drawString(this.font, "§7FPS: " + fpsColor + fps, x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 忽略
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示Java版本信息
|
|
||||||
guiGraphics.drawString(this.font, "§7Java: §f" + System.getProperty("java.version"), x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
// 显示Minecraft版本
|
|
||||||
guiGraphics.drawString(this.font, "§7MC Version: §f" + Minecraft.getInstance().getVersionType(), x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
// 显示Mod版本
|
|
||||||
guiGraphics.drawString(this.font, "§7Mod Version: §f" + CrossTeleportMod.ModInfo.VERSION, x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
// 显示最后更新时间
|
|
||||||
guiGraphics.drawString(this.font, "§7Last Update: §f" + (System.currentTimeMillis() - lastUpdateTime) + "ms ago", x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
|
|
||||||
// 显示搜索框状态
|
|
||||||
if (searchBox != null) {
|
|
||||||
String searchText = searchBox.getValue();
|
|
||||||
if (!searchText.isEmpty()) {
|
|
||||||
guiGraphics.drawString(this.font, "§7Search: §f\"" + searchText + "\" §7(§f" + searchText.length() + "§7 chars)", x, y, textColor);
|
|
||||||
y += lineHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示调试提示
|
|
||||||
guiGraphics.drawString(this.font, "§8Press 'Dev Toggle' to hide this panel", x, y, 0x888888);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前服务器名称
|
|
||||||
*/
|
|
||||||
private String getCurrentServerName() {
|
|
||||||
try {
|
|
||||||
if (Minecraft.getInstance().getCurrentServer() != null) {
|
|
||||||
return Minecraft.getInstance().getCurrentServer().name;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 忽略
|
|
||||||
}
|
|
||||||
return "SinglePlayer/Local";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取在线玩家数量
|
|
||||||
*/
|
|
||||||
private int getOnlinePlayerCount() {
|
|
||||||
try {
|
|
||||||
if (Minecraft.getInstance().getConnection() != null) {
|
|
||||||
return Minecraft.getInstance().getConnection().getOnlinePlayers().size();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
// 先检查搜索框点击 - 让搜索框获得焦点
|
|
||||||
if (searchBox != null) {
|
|
||||||
boolean clicked = searchBox.mouseClicked(mouseX, mouseY, button);
|
|
||||||
if (clicked) {
|
|
||||||
// 点击搜索框时,取消列表的焦点,并确保搜索框获得焦点
|
|
||||||
if (serverList != null) {
|
|
||||||
serverList.setFocused(false);
|
|
||||||
}
|
|
||||||
searchBox.setFocused(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 再检查按钮点击
|
|
||||||
if (enableCrCheckBox != null && enableCrCheckBox.mouseClicked(mouseX, mouseY, button)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (enablePiCheckBox != null && enablePiCheckBox.mouseClicked(mouseX, mouseY, button)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (closeButton != null && closeButton.mouseClicked(mouseX, mouseY, button)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (DEV_MODE) {
|
|
||||||
if (devTestButton != null && devTestButton.mouseClicked(mouseX, mouseY, button)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (devToggleDebugButton != null && devToggleDebugButton.mouseClicked(mouseX, mouseY, button)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后检查列表点击
|
|
||||||
if (serverList != null && serverList.mouseClicked(mouseX, mouseY, button)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击其他地方时,取消搜索框焦点
|
|
||||||
if (searchBox != null && searchBox.isFocused()) {
|
|
||||||
searchBox.setFocused(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.mouseClicked(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
|
||||||
// ESC键关闭GUI
|
|
||||||
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
|
|
||||||
this.onClose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先让搜索框处理按键(如果搜索框有焦点)
|
|
||||||
if (searchBox != null && searchBox.isFocused()) {
|
|
||||||
if (searchBox.keyPressed(keyCode, scanCode, modifiers)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.keyPressed(keyCode, scanCode, modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean charTyped(char codePoint, int modifiers) {
|
|
||||||
// 优先让搜索框处理字符输入
|
|
||||||
if (searchBox != null && searchBox.isFocused()) {
|
|
||||||
if (searchBox.charTyped(codePoint, modifiers)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.charTyped(codePoint, modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendCustomPayload(String message) {
|
|
||||||
Minecraft mc = Minecraft.getInstance();
|
Minecraft mc = Minecraft.getInstance();
|
||||||
if (mc.getConnection() != null) {
|
if (mc.getConnection() != null) {
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(256));
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(256));
|
||||||
|
|
@ -467,17 +79,38 @@ public class CrossServerGui extends Screen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderLogo(@NotNull GuiGraphics guiGraphics) {
|
@Override
|
||||||
int logoWidth = 64;
|
public void render(@NotNull PoseStack poseStack, int mouseX, int mouseY, float partialTicks) {
|
||||||
int logoHeight = 64;
|
// 背景
|
||||||
|
this.renderBackground(poseStack);
|
||||||
|
|
||||||
int x = (this.width - logoWidth - font.width(this.title.getString()) * 2) / 2;
|
// Logo 渲染(缩放绘制)
|
||||||
int y = -5;
|
renderLogo(poseStack);
|
||||||
guiGraphics.blit(LOGO_TEXTURE, x, y, 0, 0, logoWidth, logoHeight, logoWidth, logoHeight);
|
|
||||||
|
// 渲染标题文字
|
||||||
|
drawCenteredString(poseStack, this.font, this.title.getString(), this.width / 2 + 5, 10, 0xFFFFFF);
|
||||||
|
|
||||||
|
// 渲染按钮等组件
|
||||||
|
super.render(poseStack, mouseX, mouseY, partialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderLogo(PoseStack poseStack) {
|
||||||
|
RenderSystem.setShader(GameRenderer::getPositionTexShader);
|
||||||
|
RenderSystem.setShaderTexture(0, LOGO_TEXTURE);
|
||||||
|
RenderSystem.enableDepthTest();
|
||||||
|
|
||||||
|
|
||||||
|
int logoWidth = 100; // 你可以改成 150、200 等
|
||||||
|
int logoHeight = 100; // 保持比例缩放
|
||||||
|
|
||||||
|
int x = (this.width - logoWidth) / 2;
|
||||||
|
int y = 15;
|
||||||
|
|
||||||
|
blit(poseStack, x, y, 0, 0, logoWidth, logoHeight, logoWidth, logoHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPauseScreen() {
|
public boolean isPauseScreen() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.leisuretimedock.crossmod.client.gui;
|
package com.leisuretimedock.crossmod.client.gui;
|
||||||
|
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.*;
|
||||||
import net.minecraft.client.gui.screens.GenericDirtMessageScreen;
|
import net.minecraft.client.gui.screens.GenericDirtMessageScreen;
|
||||||
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
|
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
|
||||||
|
import net.minecraft.client.renderer.GameRenderer;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraftforge.client.event.ScreenEvent;
|
import net.minecraftforge.client.event.ScreenEvent;
|
||||||
|
|
@ -24,24 +26,32 @@ public class GenericIceMessageScreen extends GenericDirtMessageScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
public void render(@NotNull PoseStack poseStack, int mouseX, int mouseY, float partialTick) {
|
||||||
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
super.render(poseStack, mouseX, mouseY, partialTick);
|
||||||
if (minecraft != null && minecraft.player != null){
|
if (minecraft != null && minecraft.player != null){
|
||||||
InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, width / 2, height / 2, 30, (float) width / 2 - mouseX, (float) height / 2 - mouseY, minecraft.player);
|
InventoryScreen.renderEntityInInventory(width / 2, height / 2, 30, (float) width / 2 - mouseX, (float) height / 2 - mouseY, minecraft.player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderDirtBackground(@NotNull GuiGraphics guiGraphics) {
|
public void renderDirtBackground(int vOffset) {
|
||||||
renderIceBackground(guiGraphics,1.0F, 1.0F, 1.0F, 1.0F);
|
renderIceBackground(vOffset,200, 200, 200, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
public void renderIceBackground(int vOffset, int r, int g, int b, int a) {
|
||||||
public void renderIceBackground(@NotNull GuiGraphics guiGraphics, float r, float g, float b, float a) {
|
Tesselator tesselator = Tesselator.getInstance();
|
||||||
guiGraphics.setColor(0.65F, 0.65F, 0.65F, 1.0F);
|
BufferBuilder bufferbuilder = tesselator.getBuilder();
|
||||||
guiGraphics.blit(ICE, 0, 0, 0, 0.0F, 0.0F, this.width, this.height, 32, 32);
|
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
|
||||||
guiGraphics.setColor(r, g, b, a);
|
RenderSystem.setShaderTexture(0, ICE);
|
||||||
MinecraftForge.EVENT_BUS.post(new ScreenEvent.BackgroundRendered(this, guiGraphics));
|
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||||
|
float f = 32.0F;
|
||||||
|
bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
|
||||||
|
bufferbuilder.vertex(0.0, this.height, 0.0).uv(0.0F, (float)this.height / f + (float)vOffset).color(r, g, b, a).endVertex();
|
||||||
|
bufferbuilder.vertex(this.width, this.height, 0.0).uv((float)this.width / f, (float)this.height / f + (float)vOffset).color(r, g, b, a).endVertex();
|
||||||
|
bufferbuilder.vertex(this.width, 0.0, 0.0).uv((float)this.width / f, (float)vOffset).color(r, g, b, a).endVertex();
|
||||||
|
bufferbuilder.vertex(0.0, 0.0, 0.0).uv(0.0F, (float)vOffset).color(r, g, b, a).endVertex();
|
||||||
|
tesselator.end();
|
||||||
|
MinecraftForge.EVENT_BUS.post(new ScreenEvent.BackgroundDrawnEvent(this, new PoseStack()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,341 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.client.gui;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.client.KeyBindingHandler;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.components.Button;
|
|
||||||
import net.minecraft.client.gui.components.ObjectSelectionList;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class ServerSelectionList extends ObjectSelectionList<ServerSelectionList.ServerEntry> {
|
|
||||||
private final CrossServerGui parentScreen;
|
|
||||||
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\w+)\\}");
|
|
||||||
private static final Pattern FORMAT_PATTERN = Pattern.compile("%([dsf])");
|
|
||||||
|
|
||||||
// 存储所有服务器条目用于过滤
|
|
||||||
private final List<ServerEntry> allEntries = new ArrayList<>();
|
|
||||||
private String searchFilter = "";
|
|
||||||
|
|
||||||
public ServerSelectionList(CrossServerGui parent, Minecraft mc, int width, int height, int y0, int y1, int itemHeight, @NotNull Map<String, String> servers) {
|
|
||||||
super(mc, width, height, y0, y1, itemHeight);
|
|
||||||
this.parentScreen = parent;
|
|
||||||
|
|
||||||
// 创建所有条目
|
|
||||||
if (servers.isEmpty()) {
|
|
||||||
ServerEntry entry = new ServerEntry(Component.translatable("ltd.mod.client.menu.button.no_servers"), null, parentScreen);
|
|
||||||
allEntries.add(entry);
|
|
||||||
this.addEntry(entry);
|
|
||||||
} else {
|
|
||||||
servers.forEach((serverId, translateKey) -> {
|
|
||||||
Component displayName = formatComponent(translateKey, serverId);
|
|
||||||
ServerEntry entry = new ServerEntry(displayName, serverId, parentScreen);
|
|
||||||
allEntries.add(entry);
|
|
||||||
this.addEntry(entry);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置搜索过滤器
|
|
||||||
*/
|
|
||||||
public void setSearchFilter(String filterText) {
|
|
||||||
this.searchFilter = filterText == null ? "" : filterText.trim().toLowerCase();
|
|
||||||
refreshEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新列表显示(应用当前过滤)
|
|
||||||
*/
|
|
||||||
private void refreshEntries() {
|
|
||||||
this.clearEntries();
|
|
||||||
|
|
||||||
if (searchFilter.isEmpty()) {
|
|
||||||
// 无过滤,显示所有条目
|
|
||||||
allEntries.forEach(this::addEntry);
|
|
||||||
} else {
|
|
||||||
// 过滤条目
|
|
||||||
boolean hasMatch = false;
|
|
||||||
for (ServerEntry entry : allEntries) {
|
|
||||||
if (entry.serverId == null) {
|
|
||||||
// "无服务器"条目只在没有其他条目时显示
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String displayText = entry.displayName.getString().toLowerCase();
|
|
||||||
String serverId = entry.serverId.toLowerCase();
|
|
||||||
if (displayText.contains(searchFilter) || serverId.contains(searchFilter)) {
|
|
||||||
this.addEntry(entry);
|
|
||||||
hasMatch = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果没有匹配的条目,显示提示
|
|
||||||
if (!hasMatch && !allEntries.isEmpty()) {
|
|
||||||
Component noMatch = Component.literal("§7没有匹配的服务器: " + searchFilter);
|
|
||||||
this.addEntry(new ServerEntry(noMatch, null, parentScreen));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Component formatComponent(String input, String serverId) {
|
|
||||||
if (input == null || input.isEmpty()) {
|
|
||||||
return Component.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 检查是否包含格式化参数
|
|
||||||
if (input.contains("?")) {
|
|
||||||
String[] parts = input.split("\\?", 2);
|
|
||||||
String key = parts[0];
|
|
||||||
String paramString = parts[1];
|
|
||||||
|
|
||||||
// 解析参数
|
|
||||||
Map<String, String> params = parseParameters(paramString);
|
|
||||||
|
|
||||||
// 获取翻译文本
|
|
||||||
String template = Component.translatable(key).getString();
|
|
||||||
|
|
||||||
// 先替换 {placeholder} 格式
|
|
||||||
String formatted = replacePlaceholders(template, params, serverId);
|
|
||||||
|
|
||||||
// 再处理 %s, %d, %f 格式
|
|
||||||
formatted = handleStringFormat(formatted, params, serverId);
|
|
||||||
|
|
||||||
return Component.literal(formatted);
|
|
||||||
} else {
|
|
||||||
// 没有参数,直接翻译
|
|
||||||
return Component.translatable(input);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 出错时回退到原始文本
|
|
||||||
return Component.literal(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull Map<String, String> parseParameters(@NotNull String paramString) {
|
|
||||||
Map<String, String> params = new HashMap<>();
|
|
||||||
String[] pairs = paramString.split("&");
|
|
||||||
|
|
||||||
for (String pair : pairs) {
|
|
||||||
String[] kv = pair.split("=", 2);
|
|
||||||
if (kv.length == 2) {
|
|
||||||
params.put(kv[0], kv[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull String replacePlaceholders(String template, Map<String, String> params, String serverId) {
|
|
||||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
|
|
||||||
while (matcher.find()) {
|
|
||||||
String placeholder = matcher.group(1);
|
|
||||||
String replacement = getReplacement(placeholder, params, serverId);
|
|
||||||
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
|
||||||
}
|
|
||||||
matcher.appendTail(result);
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getReplacement(String placeholder, @NotNull Map<String, String> params, String serverId) {
|
|
||||||
// 首先检查参数映射
|
|
||||||
if (params.containsKey(placeholder)) {
|
|
||||||
String value = params.get(placeholder);
|
|
||||||
// 如果值是占位符,递归处理
|
|
||||||
if (value != null && value.startsWith("{") && value.endsWith("}")) {
|
|
||||||
String innerPlaceholder = value.substring(1, value.length() - 1);
|
|
||||||
return getReplacement(innerPlaceholder, params, serverId);
|
|
||||||
}
|
|
||||||
return value != null ? value : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内置占位符
|
|
||||||
return switch (placeholder) {
|
|
||||||
case "serverId" -> serverId != null ? serverId : "Unknown Server";
|
|
||||||
case "player" -> {
|
|
||||||
try {
|
|
||||||
yield Minecraft.getInstance().getUser().getName();
|
|
||||||
} catch (Exception e) {
|
|
||||||
yield "Unknown Player";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "playerCount" -> String.valueOf(getOnlinePlayerCount());
|
|
||||||
case "serverName" -> Minecraft.getInstance().getCurrentServer() != null ?
|
|
||||||
Minecraft.getInstance().getCurrentServer().name : "Unknown Server";
|
|
||||||
case "currentTime" -> String.valueOf(System.currentTimeMillis());
|
|
||||||
case "ip" -> Minecraft.getInstance().getCurrentServer() != null ?
|
|
||||||
Minecraft.getInstance().getCurrentServer().ip : "Unknown IP";
|
|
||||||
case "version" -> Minecraft.getInstance().getVersionType();
|
|
||||||
case "key" -> {
|
|
||||||
try {
|
|
||||||
// 获取打开菜单的按键名称
|
|
||||||
yield KeyBindingHandler.OPEN_GUI_KEY.getKey().getDisplayName().getString();
|
|
||||||
} catch (Exception e) {
|
|
||||||
yield "Unknown Key";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default -> "{" + placeholder + "}";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String handleStringFormat(String text, Map<String, String> params, String serverId) {
|
|
||||||
Matcher matcher = FORMAT_PATTERN.matcher(text);
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
while (matcher.find()) {
|
|
||||||
String formatType = matcher.group(1);
|
|
||||||
String replacement = getStringFormatReplacement(formatType, index, params, serverId);
|
|
||||||
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
matcher.appendTail(result);
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getStringFormatReplacement(String formatType, int index, Map<String, String> params, String serverId) {
|
|
||||||
// 尝试从参数中获取值
|
|
||||||
String paramValue = null;
|
|
||||||
|
|
||||||
// 尝试不同的参数键名
|
|
||||||
String[] possibleKeys = {
|
|
||||||
"arg" + index,
|
|
||||||
"param" + index,
|
|
||||||
"p" + index,
|
|
||||||
String.valueOf(index)
|
|
||||||
};
|
|
||||||
|
|
||||||
for (String key : possibleKeys) {
|
|
||||||
if (params.containsKey(key)) {
|
|
||||||
paramValue = params.get(key);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有找到参数,使用默认值
|
|
||||||
if (paramValue == null) {
|
|
||||||
paramValue = getDefaultFormattedValue(index, serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据格式类型格式化
|
|
||||||
try {
|
|
||||||
return switch (formatType) {
|
|
||||||
case "d" -> {
|
|
||||||
try {
|
|
||||||
yield String.valueOf(Integer.parseInt(paramValue));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
yield "0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "f" -> {
|
|
||||||
try {
|
|
||||||
yield String.format("%.1f", Double.parseDouble(paramValue));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
yield "0.0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default -> paramValue;
|
|
||||||
};
|
|
||||||
} catch (Exception e) {
|
|
||||||
return paramValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDefaultFormattedValue(int index, String serverId) {
|
|
||||||
return switch (index) {
|
|
||||||
case 0 -> serverId != null ? serverId : "Server";
|
|
||||||
case 1 -> String.valueOf(getOnlinePlayerCount());
|
|
||||||
case 2 -> {
|
|
||||||
try {
|
|
||||||
yield Minecraft.getInstance().getUser().getName();
|
|
||||||
} catch (Exception e) {
|
|
||||||
yield "Player";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default -> "?";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getOnlinePlayerCount() {
|
|
||||||
try {
|
|
||||||
if (Minecraft.getInstance().getConnection() != null) {
|
|
||||||
return Minecraft.getInstance().getConnection().getOnlinePlayers().size();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
if (button == 0) {
|
|
||||||
ServerEntry entry = this.getEntryAtPosition(mouseX, mouseY);
|
|
||||||
if (entry != null && entry.serverId != null) {
|
|
||||||
parentScreen.sendCustomPayload("connect:" + entry.serverId);
|
|
||||||
parentScreen.onClose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.mouseClicked(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ServerEntry extends ObjectSelectionList.Entry<ServerEntry> {
|
|
||||||
private final Component displayName;
|
|
||||||
private final String serverId;
|
|
||||||
private Button serverButton;
|
|
||||||
|
|
||||||
public ServerEntry(Component displayName, String serverId, CrossServerGui parent) {
|
|
||||||
this.displayName = displayName;
|
|
||||||
this.serverId = serverId;
|
|
||||||
|
|
||||||
if (serverId != null) {
|
|
||||||
this.serverButton = Button.builder(displayName, button -> {
|
|
||||||
parent.sendCustomPayload("connect:" + serverId);
|
|
||||||
parent.onClose();
|
|
||||||
}).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(@NotNull GuiGraphics guiGraphics, int index, int top, int left, int width, int height,
|
|
||||||
int mouseX, int mouseY, boolean isMouseOver, float partialTick) {
|
|
||||||
|
|
||||||
if (serverButton != null) {
|
|
||||||
// 更新按钮位置和大小
|
|
||||||
serverButton.setX(left + 5);
|
|
||||||
serverButton.setY(top + 2);
|
|
||||||
serverButton.setWidth(width - 10);
|
|
||||||
serverButton.setHeight(height - 4);
|
|
||||||
|
|
||||||
// 渲染按钮
|
|
||||||
serverButton.render(guiGraphics, mouseX, mouseY, partialTick);
|
|
||||||
} else {
|
|
||||||
// "无服务器"条目
|
|
||||||
int textX = left + (width - Minecraft.getInstance().font.width(displayName)) / 2;
|
|
||||||
int textY = top + (height - Minecraft.getInstance().font.lineHeight) / 2;
|
|
||||||
guiGraphics.drawString(Minecraft.getInstance().font, displayName, textX, textY, 0xFFAAAAAA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
||||||
if (serverButton != null) {
|
|
||||||
return serverButton.mouseClicked(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Component getNarration() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,14 +4,14 @@ import com.leisuretimedock.crossmod.client.KeyBindingHandler;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.Font;
|
import net.minecraft.client.gui.Font;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiComponent;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.item.Items;
|
import net.minecraft.world.item.Items;
|
||||||
import net.minecraftforge.client.gui.overlay.ForgeGui;
|
import net.minecraftforge.client.gui.ForgeIngameGui;
|
||||||
import net.minecraftforge.client.gui.overlay.IGuiOverlay;
|
import net.minecraftforge.client.gui.IIngameOverlay;
|
||||||
|
|
||||||
public class CrossServerTipOverLay implements IGuiOverlay {
|
public class CrossServerTipOverLay implements IIngameOverlay {
|
||||||
public static final CrossServerTipOverLay INSTANCE = new CrossServerTipOverLay();
|
public static final CrossServerTipOverLay INSTANCE = new CrossServerTipOverLay();
|
||||||
private static boolean showOverlay = false;
|
private static boolean showOverlay = false;
|
||||||
private static final Minecraft mc = Minecraft.getInstance();
|
private static final Minecraft mc = Minecraft.getInstance();
|
||||||
|
|
@ -22,22 +22,24 @@ public class CrossServerTipOverLay implements IGuiOverlay {
|
||||||
showOverlay = show;
|
showOverlay = show;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void render(ForgeGui forgeGui, GuiGraphics guiGraphics, float v, int i, int i1) {
|
public void render(ForgeIngameGui forgeIngameGui, PoseStack poseStack, float v, int i, int i1) {
|
||||||
if (isShowOverlay()) return;
|
if ( !showOverlay || mc.player == null || mc.level == null) return;
|
||||||
int x = 10;
|
int x = 10;
|
||||||
int y = 10;
|
int y = 10;
|
||||||
Font font = mc.font;
|
Font font = mc.font;
|
||||||
|
ItemRenderer itemRenderer = mc.getItemRenderer();
|
||||||
|
|
||||||
// 1. 原版钟物品
|
// 1. 原版钟物品
|
||||||
ItemStack clockStack = new ItemStack(Items.CLOCK);
|
ItemStack clockStack = new ItemStack(Items.CLOCK);
|
||||||
PoseStack poseStack = new PoseStack();
|
|
||||||
poseStack.translate(10, 10, 10);
|
// 2. 渲染钟图标(含动画帧)
|
||||||
// 2. 渲染钟图标
|
itemRenderer.renderAndDecorateItem(clockStack, x, y);
|
||||||
guiGraphics.renderItem(clockStack, x, y);
|
itemRenderer.renderGuiItemDecorations(mc.font, clockStack, x, y);
|
||||||
guiGraphics.renderItemDecorations(font,clockStack, x, y);
|
|
||||||
// 3. 绘制提示文字
|
// 3. 绘制提示文字
|
||||||
String text = Component.translatable("ltd.mod.client.overlay.tip", KeyBindingHandler.OPEN_GUI_KEY.getTranslatedKeyMessage()).getString();
|
String keyText = KeyBindingHandler.OPEN_GUI_KEY.getTranslatedKeyMessage().getString(); // 可动态从 KeyMapping 获取
|
||||||
guiGraphics.drawString(font, text, x + 20, y + 6, 0xFFFFFF);
|
String text = "按 [" + keyText.toUpperCase() + "] 打开跨服传送菜单";
|
||||||
|
GuiComponent.drawString(poseStack, font, text, x + 20, y + 6, 0xFFFFFF);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,26 @@ package com.leisuretimedock.crossmod.client.overlay;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.client.event.RegisterGuiOverlaysEvent;
|
import net.minecraftforge.client.gui.OverlayRegistry;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
|
||||||
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||||
public class OverlayRenderer {
|
public class OverlayRenderer {
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRender(@NotNull RegisterGuiOverlaysEvent event) {
|
public static void onRender(FMLClientSetupEvent event) {
|
||||||
event.registerAboveAll("cross_server_tip", CrossServerTipOverLay.INSTANCE);
|
event.enqueueWork(() -> {
|
||||||
event.registerAboveAll(
|
OverlayRegistry.registerOverlayTop(
|
||||||
"ping_debug",
|
"cross_server_tip",
|
||||||
PingOverlayManager.INSTANCE
|
CrossServerTipOverLay.INSTANCE
|
||||||
);
|
);
|
||||||
|
OverlayRegistry.registerOverlayTop(
|
||||||
|
"ping_debug",
|
||||||
|
PingOverlayManager.INSTANCE
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
package com.leisuretimedock.crossmod.client.overlay;
|
package com.leisuretimedock.crossmod.client.overlay;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.client.ClientPingHandler;
|
import com.leisuretimedock.crossmod.client.ClientPingHandler;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.Font;
|
import net.minecraft.client.gui.Font;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiComponent;
|
||||||
import net.minecraftforge.client.gui.overlay.ForgeGui;
|
import net.minecraftforge.client.gui.ForgeIngameGui;
|
||||||
import net.minecraftforge.client.gui.overlay.IGuiOverlay;
|
import net.minecraftforge.client.gui.IIngameOverlay;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PingOverlayManager implements IGuiOverlay {
|
public class PingOverlayManager implements IIngameOverlay {
|
||||||
public static final PingOverlayManager INSTANCE = new PingOverlayManager();
|
public static final PingOverlayManager INSTANCE = new PingOverlayManager();
|
||||||
private static boolean showOverlay = false;
|
private static boolean showOverlay = true;
|
||||||
private static final Minecraft mc = Minecraft.getInstance();
|
private static final Minecraft mc = Minecraft.getInstance();
|
||||||
public static boolean isShowOverlay() {
|
public static boolean isShowOverlay() {
|
||||||
return !showOverlay || mc.player == null || mc.level == null;
|
return !showOverlay || mc.player == null || mc.level == null;
|
||||||
|
|
@ -29,7 +29,7 @@ public class PingOverlayManager implements IGuiOverlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(ForgeGui gui, GuiGraphics guiGraphics, float partialTick, int width, int height) {
|
public void render(ForgeIngameGui gui, PoseStack poseStack, float partialTick, int width, int height) {
|
||||||
if (!showOverlay || mc.player == null || mc.level == null) {
|
if (!showOverlay || mc.player == null || mc.level == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -50,13 +50,13 @@ public class PingOverlayManager implements IGuiOverlay {
|
||||||
int y = findSuitableYPosition(height, totalHeight);
|
int y = findSuitableYPosition(height, totalHeight);
|
||||||
|
|
||||||
// 绘制背景
|
// 绘制背景
|
||||||
drawBackground(guiGraphics, x, y, maxWidth, totalHeight, font);
|
drawBackground(poseStack, x, y, maxWidth, totalHeight, font);
|
||||||
|
|
||||||
// 绘制文本
|
// 绘制文本
|
||||||
drawTextLines(guiGraphics, font, allLines, x, y);
|
drawTextLines(gui, poseStack, font, allLines, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NotNull List<String> getAllDisplayLines() {
|
private List<String> getAllDisplayLines() {
|
||||||
List<String> lines = new ArrayList<>();
|
List<String> lines = new ArrayList<>();
|
||||||
|
|
||||||
// 添加Ping信息
|
// 添加Ping信息
|
||||||
|
|
@ -75,7 +75,7 @@ public class PingOverlayManager implements IGuiOverlay {
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMaxLineWidth(@NotNull Font font, @NotNull List<String> lines) {
|
private int getMaxLineWidth(Font font, List<String> lines) {
|
||||||
return lines.stream()
|
return lines.stream()
|
||||||
.mapToInt(font::width)
|
.mapToInt(font::width)
|
||||||
.max()
|
.max()
|
||||||
|
|
@ -95,18 +95,18 @@ public class PingOverlayManager implements IGuiOverlay {
|
||||||
return baseY;
|
return baseY;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawBackground(@NotNull GuiGraphics guiGraphics, int x, int y, int width, int height, Font font) {
|
private void drawBackground(PoseStack poseStack, int x, int y, int width, int height, Font font) {
|
||||||
guiGraphics.fill(
|
GuiComponent.fill(poseStack,
|
||||||
x - PADDING, y - PADDING,
|
x - PADDING, y - PADDING,
|
||||||
x + width + PADDING, y + height + PADDING,
|
x + width + PADDING, y + height + PADDING,
|
||||||
BACKGROUND_COLOR);
|
BACKGROUND_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawTextLines(GuiGraphics guiGraphics, Font font, @NotNull List<String> lines, int x, int y) {
|
private void drawTextLines(ForgeIngameGui gui, PoseStack poseStack, Font font, List<String> lines, int x, int y) {
|
||||||
for (int i = 0; i < lines.size(); i++) {
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
String line = lines.get(i);
|
String line = lines.get(i);
|
||||||
if (!line.isEmpty()) {
|
if (!line.isEmpty()) {
|
||||||
guiGraphics.drawString(font, line,
|
gui.getFont().draw(poseStack, line,
|
||||||
x,
|
x,
|
||||||
y + i * font.lineHeight,
|
y + i * font.lineHeight,
|
||||||
TEXT_COLOR);
|
TEXT_COLOR);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package com.leisuretimedock.crossmod.client.overlay;
|
package com.leisuretimedock.crossmod.client.overlay;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
@ -13,7 +11,7 @@ public enum PluginCommand {
|
||||||
|
|
||||||
PluginCommand(String id) { this.id = id; }
|
PluginCommand(String id) { this.id = id; }
|
||||||
|
|
||||||
public static @NotNull Optional<PluginCommand> fromId(String id) {
|
public static Optional<PluginCommand> fromId(String id) {
|
||||||
return Arrays.stream(values()).filter(cmd -> cmd.id.equals(id)).findFirst();
|
return Arrays.stream(values()).filter(cmd -> cmd.id.equals(id)).findFirst();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.command;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
|
||||||
import com.mojang.brigadier.arguments.BoolArgumentType;
|
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
|
||||||
import net.minecraft.commands.Commands;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.network.chat.MutableComponent;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public class CrossModDebugCommand {
|
|
||||||
|
|
||||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
|
||||||
dispatcher.register(Commands.literal("crossmod")
|
|
||||||
.then(Commands.literal("debug")
|
|
||||||
.then(Commands.literal("packets")
|
|
||||||
.then(Commands.argument("enabled", BoolArgumentType.bool())
|
|
||||||
.executes(ctx -> {
|
|
||||||
boolean enabled = BoolArgumentType.getBool(ctx, "enabled");
|
|
||||||
CrossCommonModConfig.getInstance().setDebugPackets(enabled);
|
|
||||||
|
|
||||||
MutableComponent message;
|
|
||||||
if (enabled) {
|
|
||||||
message = Component.translatable("crossmod.command.debug.packets.enabled");
|
|
||||||
} else {
|
|
||||||
message = Component.translatable("crossmod.command.debug.packets.disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.getSource().sendSuccess(() -> message, true);
|
|
||||||
return 1;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(Commands.literal("bundledebug")
|
|
||||||
.then(Commands.argument("enabled", BoolArgumentType.bool())
|
|
||||||
.executes(ctx -> {
|
|
||||||
boolean enabled = BoolArgumentType.getBool(ctx, "enabled");
|
|
||||||
CrossCommonModConfig.getInstance().setDebugBundleDelimiter(enabled);
|
|
||||||
|
|
||||||
MutableComponent message;
|
|
||||||
if (enabled) {
|
|
||||||
message = Component.translatable("crossmod.command.debug.bundledebug.enabled");
|
|
||||||
} else {
|
|
||||||
message = Component.translatable("crossmod.command.debug.bundledebug.disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.getSource().sendSuccess(() -> message, true);
|
|
||||||
return 1;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(Commands.literal("status")
|
|
||||||
.executes(ctx -> {
|
|
||||||
CrossCommonModConfig config = CrossCommonModConfig.getInstance();
|
|
||||||
|
|
||||||
MutableComponent status = Component.literal("")
|
|
||||||
.append(Component.translatable("crossmod.command.debug.status.title"))
|
|
||||||
.append("\n §6")
|
|
||||||
.append(Component.translatable("crossmod.command.debug.status.packet"))
|
|
||||||
.append(getStatusText(config.isDebugPackets()))
|
|
||||||
.append("\n §6")
|
|
||||||
.append(Component.translatable("crossmod.command.debug.status.bundledebug"))
|
|
||||||
.append(getStatusText(config.isDebugBundleDelimiter()))
|
|
||||||
.append("\n §6")
|
|
||||||
.append(Component.translatable("crossmod.command.debug.status.maxbytes"))
|
|
||||||
.append("§f")
|
|
||||||
.append(String.valueOf(config.getMaxDebugBytes()))
|
|
||||||
.append("\n §6")
|
|
||||||
.append(Component.translatable("crossmod.command.debug.status.logfile"))
|
|
||||||
.append(getStatusText(config.isLogToFile()));
|
|
||||||
|
|
||||||
ctx.getSource().sendSuccess(() -> status, false);
|
|
||||||
return 1;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull MutableComponent getStatusText(boolean enabled) {
|
|
||||||
if (enabled) {
|
|
||||||
return Component.translatable("crossmod.command.debug.enabled_short");
|
|
||||||
} else {
|
|
||||||
return Component.translatable("crossmod.command.debug.disabled_short");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.command;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
|
||||||
import com.leisuretimedock.crossmod.network.toClient.GotoServerPayload;
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
|
||||||
import net.minecraft.commands.Commands;
|
|
||||||
import net.minecraft.commands.arguments.EntityArgument;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public class GotoServerCommand {
|
|
||||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
|
||||||
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("cross")
|
|
||||||
.then(
|
|
||||||
Commands.argument("players", EntityArgument.players())
|
|
||||||
.requires(cs -> cs.hasPermission(2))
|
|
||||||
.then(Commands.literal("goto")
|
|
||||||
.then(Commands.argument("server", StringArgumentType.string())
|
|
||||||
.executes(ctx -> {
|
|
||||||
String server = StringArgumentType.getString(ctx, "server");
|
|
||||||
Collection<ServerPlayer> players = EntityArgument.getPlayers(ctx, "players");
|
|
||||||
players.forEach(p -> NetworkHandler.sendToPlayer(new GotoServerPayload(server), p));
|
|
||||||
ctx.getSource().sendSuccess(
|
|
||||||
() -> Component.translatable("ltd.mod.client.request.goto",server), false);
|
|
||||||
return 1;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(Commands.literal("goto")
|
|
||||||
.then(Commands.argument("server", StringArgumentType.string())
|
|
||||||
.executes(ctx -> {
|
|
||||||
CommandSourceStack source = ctx.getSource();
|
|
||||||
ServerPlayer player = source.getPlayer();
|
|
||||||
if (player != null) {
|
|
||||||
String server = StringArgumentType.getString(ctx, "server");
|
|
||||||
NetworkHandler.sendToPlayer(new GotoServerPayload(server), player);
|
|
||||||
source.sendSuccess(
|
|
||||||
() -> Component.translatable("ltd.mod.client.request.goto",server), false);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
source.sendFailure(Component.literal("Request a player"));
|
|
||||||
return 1;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
dispatcher.register(main);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,9 +11,8 @@ import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.Commands;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.commands.arguments.EntityArgument;
|
import net.minecraft.commands.arguments.EntityArgument;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -21,7 +20,7 @@ import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class PingCommand {
|
public class PingCommand {
|
||||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
LiteralArgumentBuilder<CommandSourceStack> networkping =
|
LiteralArgumentBuilder<CommandSourceStack> networkping =
|
||||||
Commands.literal("netping")
|
Commands.literal("netping")
|
||||||
.requires(source -> source.hasPermission(2))
|
.requires(source -> source.hasPermission(2))
|
||||||
|
|
@ -91,9 +90,9 @@ public class PingCommand {
|
||||||
dispatcher.register(networkping);
|
dispatcher.register(networkping);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executePlayerReport(CommandSourceStack source, @NotNull Collection<ServerPlayer> players) throws CommandSyntaxException {
|
private static int executePlayerReport(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +100,7 @@ public class PingCommand {
|
||||||
Map<UUID, Long> results = PingRequestManager.getLatestPingsForPlayers(players);
|
Map<UUID, Long> results = PingRequestManager.getLatestPingsForPlayers(players);
|
||||||
|
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,12 +110,12 @@ public class PingCommand {
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executeFullReport(@NotNull CommandSourceStack source) throws CommandSyntaxException {
|
private static int executeFullReport(CommandSourceStack source) throws CommandSyntaxException {
|
||||||
ServerPlayer player = source.getPlayerOrException();
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
Map<UUID, Long> results = PingRequestManager.getAllLatestPings();
|
Map<UUID, Long> results = PingRequestManager.getAllLatestPings();
|
||||||
|
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,12 +129,12 @@ public class PingCommand {
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executeStatsReport(@NotNull CommandSourceStack source) throws CommandSyntaxException {
|
private static int executeStatsReport(CommandSourceStack source) throws CommandSyntaxException {
|
||||||
ServerPlayer player = source.getPlayerOrException();
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats();
|
PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats();
|
||||||
|
|
||||||
if (stats.sampleCount() == 0) {
|
if (stats.sampleCount() == 0) {
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,31 +144,31 @@ public class PingCommand {
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executeSinglePing(@NotNull CommandSourceStack source) throws CommandSyntaxException {
|
private static int executeSinglePing(CommandSourceStack source) throws CommandSyntaxException {
|
||||||
ServerPlayer player = source.getPlayerOrException();
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
if(!PingRequestManager.isMonitored(player.getUUID())) {
|
if(!PingRequestManager.isMonitored(player.getUUID())) {
|
||||||
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.self"));
|
source.sendFailure(new TranslatableComponent("ltd.mod.ping.error.not_monitored.self"));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
PingRequestManager.ping(player);
|
PingRequestManager.ping(player);
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_self"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_self"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executePingPlayers(CommandSourceStack source, @NotNull Collection<ServerPlayer> players) throws CommandSyntaxException {
|
private static int executePingPlayers(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
players.forEach(player -> {
|
players.forEach(player -> {
|
||||||
if(!PingRequestManager.isMonitored(player.getUUID())) {
|
if(!PingRequestManager.isMonitored(player.getUUID())) {
|
||||||
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.other",
|
source.sendFailure(new TranslatableComponent("ltd.mod.ping.error.not_monitored.other",
|
||||||
player.getScoreboardName()));
|
player.getScoreboardName()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PingRequestManager.ping(player);
|
PingRequestManager.ping(player);
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_other",
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_other",
|
||||||
player.getScoreboardName()), false);
|
player.getScoreboardName()), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -177,11 +176,11 @@ public class PingCommand {
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
private static int executeMultiPing(CommandSourceStack source,
|
private static int executeMultiPing(CommandSourceStack source,
|
||||||
@NotNull Collection<ServerPlayer> players,
|
Collection<ServerPlayer> players,
|
||||||
int count,
|
int count,
|
||||||
int interval) {
|
int interval) {
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,12 +188,12 @@ public class PingCommand {
|
||||||
if (PingRequestManager.sendMultiplePings(player, count, interval)) {
|
if (PingRequestManager.sendMultiplePings(player, count, interval)) {
|
||||||
source.sendSuccess(
|
source.sendSuccess(
|
||||||
player.getScoreboardName().equals(source.getTextName()) ?
|
player.getScoreboardName().equals(source.getTextName()) ?
|
||||||
() -> Component.translatable("ltd.mod.ping.success.multiping.start.self", count, interval) :
|
new TranslatableComponent("ltd.mod.ping.success.multiping.start.self", count, interval) :
|
||||||
() -> Component.translatable("ltd.mod.ping.success.multiping.start.other", player.getScoreboardName(), count, interval),
|
new TranslatableComponent("ltd.mod.ping.success.multiping.start.other", player.getScoreboardName(), count, interval),
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
source.sendFailure(
|
source.sendFailure(
|
||||||
Component.translatable(
|
new TranslatableComponent(
|
||||||
player.getScoreboardName().equals(source.getTextName()) ?
|
player.getScoreboardName().equals(source.getTextName()) ?
|
||||||
"ltd.mod.ping.error.multiping.fail.self" :
|
"ltd.mod.ping.error.multiping.fail.self" :
|
||||||
"ltd.mod.ping.error.multiping.fail.other",
|
"ltd.mod.ping.error.multiping.fail.other",
|
||||||
|
|
@ -206,32 +205,32 @@ public class PingCommand {
|
||||||
|
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
private static int executeToggleMonitoring(@NotNull CommandSourceStack source, boolean monitor) throws CommandSyntaxException {
|
private static int executeToggleMonitoring(CommandSourceStack source, boolean monitor) throws CommandSyntaxException {
|
||||||
ServerPlayer player = source.getPlayerOrException();
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
if (monitor) {
|
if (monitor) {
|
||||||
PingRequestManager.monitor(player);
|
PingRequestManager.monitor(player);
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.monitor.self"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.monitor.self"), false);
|
||||||
} else {
|
} else {
|
||||||
PingRequestManager.unmonitor(player);
|
PingRequestManager.unmonitor(player);
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.unmonitor.self"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.unmonitor.self"), false);
|
||||||
}
|
}
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executeToggleMonitoring(CommandSourceStack source, @NotNull Collection<ServerPlayer> players, boolean monitor) throws CommandSyntaxException {
|
private static int executeToggleMonitoring(CommandSourceStack source, Collection<ServerPlayer> players, boolean monitor) throws CommandSyntaxException {
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
players.forEach(player -> {
|
players.forEach(player -> {
|
||||||
if (monitor) {
|
if (monitor) {
|
||||||
PingRequestManager.monitor(player);
|
PingRequestManager.monitor(player);
|
||||||
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.other",
|
source.sendFailure(new TranslatableComponent("ltd.mod.ping.error.not_monitored.other",
|
||||||
player.getScoreboardName()));
|
player.getScoreboardName()));
|
||||||
} else {
|
} else {
|
||||||
PingRequestManager.unmonitor(player);
|
PingRequestManager.unmonitor(player);
|
||||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_other",
|
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_other",
|
||||||
player.getScoreboardName()), false);
|
player.getScoreboardName()), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -239,38 +238,38 @@ public class PingCommand {
|
||||||
return players.size();
|
return players.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendTextReport(@NotNull ServerPlayer player, @NotNull Map<UUID, Long> results) {
|
private static void sendTextReport(ServerPlayer player, Map<UUID, Long> results) {
|
||||||
player.displayClientMessage(Component.translatable("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD),
|
player.sendMessage(new TranslatableComponent("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD),
|
||||||
true);
|
player.getUUID());
|
||||||
|
|
||||||
results.forEach((uuid, ping) -> {
|
results.forEach((uuid, ping) -> {
|
||||||
player.displayClientMessage(
|
player.sendMessage(
|
||||||
Component.translatable(
|
new TranslatableComponent(
|
||||||
"ltd.mod.ping.report.entry",
|
"ltd.mod.ping.report.entry",
|
||||||
uuid.toString().substring(0, 8),
|
uuid.toString().substring(0, 8),
|
||||||
ping,
|
ping,
|
||||||
PingRequestManager.getAverageLatency(uuid),
|
PingRequestManager.getAverageLatency(uuid),
|
||||||
PingRequestManager.getPacketLossRate(uuid)),
|
PingRequestManager.getPacketLossRate(uuid)),
|
||||||
true
|
player.getUUID()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendStatsTextReport(@NotNull ServerPlayer player, PingRequestManager.@NotNull PingStats stats) {
|
private static void sendStatsTextReport(ServerPlayer player, PingRequestManager.PingStats stats) {
|
||||||
player.displayClientMessage(Component.translatable("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD),
|
player.sendMessage(new TranslatableComponent("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD),
|
||||||
true);
|
player.getUUID());
|
||||||
player.displayClientMessage(Component.translatable(
|
player.sendMessage(new TranslatableComponent(
|
||||||
"ltd.mod.ping.stats.average", stats.average()), true);
|
"ltd.mod.ping.stats.average", stats.average()), player.getUUID());
|
||||||
player.displayClientMessage(Component.translatable(
|
player.sendMessage(new TranslatableComponent(
|
||||||
"ltd.mod.ping.stats.max", stats.max()), true);
|
"ltd.mod.ping.stats.max", stats.max()), player.getUUID());
|
||||||
player.displayClientMessage(Component.translatable(
|
player.sendMessage(new TranslatableComponent(
|
||||||
"ltd.mod.ping.stats.min", stats.max()), true);
|
"ltd.mod.ping.stats.min", stats.max()), player.getUUID());
|
||||||
player.displayClientMessage(Component.translatable(
|
player.sendMessage(new TranslatableComponent(
|
||||||
"ltd.mod.ping.stats.avg_latency", stats.averageLatency()), true);
|
"ltd.mod.ping.stats.avg_latency", stats.averageLatency()), player.getUUID());
|
||||||
player.displayClientMessage(Component.translatable(
|
player.sendMessage(new TranslatableComponent(
|
||||||
"ltd.mod.ping.stats.packet_loss", stats.packetLossRate()), true);
|
"ltd.mod.ping.stats.packet_loss", stats.packetLossRate()), player.getUUID());
|
||||||
player.displayClientMessage(Component.translatable(
|
player.sendMessage(new TranslatableComponent(
|
||||||
"ltd.mod.ping.stats.sample_count", stats.sampleCount()), true);
|
"ltd.mod.ping.stats.sample_count", stats.sampleCount()), player.getUUID());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.config;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import net.minecraftforge.fml.loading.FMLPaths;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public class CrossCommonModConfig {
|
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
private static final Path CONFIG_PATH = FMLPaths.CONFIGDIR.get().resolve("crossmod-common.json");
|
|
||||||
|
|
||||||
private static CrossCommonModConfig INSTANCE;
|
|
||||||
|
|
||||||
// 配置项
|
|
||||||
private boolean debugPackets = false;
|
|
||||||
private boolean debugBundleDelimiter = false;
|
|
||||||
private int maxDebugBytes = 256;
|
|
||||||
private boolean logToFile = false;
|
|
||||||
private String logFilePath = "crossmod-debug.log";
|
|
||||||
|
|
||||||
// 单例模式
|
|
||||||
public static CrossCommonModConfig getInstance() {
|
|
||||||
if (INSTANCE == null) {
|
|
||||||
INSTANCE = loadConfig();
|
|
||||||
}
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CrossCommonModConfig loadConfig() {
|
|
||||||
if (Files.exists(CONFIG_PATH)) {
|
|
||||||
try (Reader reader = new FileReader(CONFIG_PATH.toFile())) {
|
|
||||||
return GSON.fromJson(reader, CrossCommonModConfig.class);
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("[CrossMod] Failed to load config, using defaults: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CrossCommonModConfig config = new CrossCommonModConfig();
|
|
||||||
config.saveConfig();
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveConfig() {
|
|
||||||
try (Writer writer = new FileWriter(CONFIG_PATH.toFile())) {
|
|
||||||
GSON.toJson(this, writer);
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("[CrossMod] Failed to save config: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
public boolean isDebugPackets() { return debugPackets; }
|
|
||||||
public boolean isDebugBundleDelimiter() { return debugBundleDelimiter; }
|
|
||||||
public int getMaxDebugBytes() { return maxDebugBytes; }
|
|
||||||
public boolean isLogToFile() { return logToFile; }
|
|
||||||
public String getLogFilePath() { return logFilePath; }
|
|
||||||
|
|
||||||
// Setters(用于热重载)
|
|
||||||
public void setDebugPackets(boolean debugPackets) {
|
|
||||||
this.debugPackets = debugPackets;
|
|
||||||
saveConfig();
|
|
||||||
}
|
|
||||||
public void setDebugBundleDelimiter(boolean debugBundleDelimiter) {
|
|
||||||
this.debugBundleDelimiter = debugBundleDelimiter;
|
|
||||||
saveConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.config;
|
|
||||||
|
|
||||||
import net.minecraftforge.common.ForgeConfigSpec;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class CrossServerConfig {
|
|
||||||
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
|
|
||||||
public static ForgeConfigSpec SPEC;
|
|
||||||
public static final ForgeConfigSpec.ConfigValue<List<? extends String>> SERVER_LIST;
|
|
||||||
public static final ForgeConfigSpec.BooleanValue DISABLED_JOIN_QUIT_MESSAGE;
|
|
||||||
|
|
||||||
static {
|
|
||||||
BUILDER.comment("Cross Server Config").push("servers");
|
|
||||||
SERVER_LIST = BUILDER
|
|
||||||
.comment(
|
|
||||||
"Server list in format: <server_name>: <translate_key>",
|
|
||||||
"",
|
|
||||||
"Supported formats:",
|
|
||||||
" 1. Translation key: lobby: ltd.mod.client.menu.button.hub",
|
|
||||||
" 2. Translation key with parameters: survival: ltd.mod.client.menu.button.survival?player={player}&count={playerCount}",
|
|
||||||
" 3. Direct text: custom1: H Custom Lobby",
|
|
||||||
" 4. Direct text with color: custom2: §a[Hub] §fServer",
|
|
||||||
"",
|
|
||||||
"Available placeholders:",
|
|
||||||
" {player} - Current player name",
|
|
||||||
" {playerCount} - Online players count",
|
|
||||||
" {serverId} - Server ID",
|
|
||||||
" {serverName} - Current server name",
|
|
||||||
" {currentTime} - Current timestamp",
|
|
||||||
" {ip} - Server IP",
|
|
||||||
" {version} - Minecraft version",
|
|
||||||
" {key} - Hotkey name",
|
|
||||||
"",
|
|
||||||
"Examples:",
|
|
||||||
" lobby: ltd.mod.client.menu.button.hub",
|
|
||||||
" survival: ltd.mod.client.menu.button.survival?player={player}",
|
|
||||||
" custom: H My Server",
|
|
||||||
" test: ltd.mod.client.menu.button.test?player={player}&count={playerCount}"
|
|
||||||
)
|
|
||||||
.defineList("serverList",
|
|
||||||
Arrays.asList(
|
|
||||||
"lobby: ltd.mod.client.menu.button.hub",
|
|
||||||
"survival: ltd.mod.client.menu.button.survival",
|
|
||||||
"skyblock: ltd.mod.client.menu.button.skyblock",
|
|
||||||
"resource: ltd.mod.client.menu.button.resource",
|
|
||||||
"minigame: ltd.mod.client.menu.button.minigame"
|
|
||||||
),
|
|
||||||
obj -> obj instanceof String str && CrossServerConfigManager.SYNTAX.matcher(str).matches()
|
|
||||||
);
|
|
||||||
BUILDER.pop();
|
|
||||||
|
|
||||||
DISABLED_JOIN_QUIT_MESSAGE = BUILDER
|
|
||||||
.comment("Disable join or quit message")
|
|
||||||
.define("disabled_join_quit_message", false);
|
|
||||||
|
|
||||||
SPEC = BUILDER.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,334 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.config;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
|
||||||
import com.leisuretimedock.crossmod.network.toClient.CommonConfigHashInformPacket;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static java.util.regex.Pattern.compile;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class CrossServerConfigManager {
|
|
||||||
public static CrossServerConfigManager INSTANCE = new CrossServerConfigManager();
|
|
||||||
|
|
||||||
// 支持更灵活的格式: server_name: translate_key?param1=value1¶m2=value2
|
|
||||||
// 或者 server_name: translate_key
|
|
||||||
// 或者 server_name: 直接文本
|
|
||||||
public static final Pattern SYNTAX = compile(
|
|
||||||
"^([a-zA-Z]\\w+):\\s*(.+)$" // 匹配 server_name: 后面的任何内容
|
|
||||||
);
|
|
||||||
|
|
||||||
// 用于解析参数
|
|
||||||
private static final Pattern PARAM_PATTERN = compile("\\?(.+)$");
|
|
||||||
private static final Pattern KEY_PATTERN = compile("^([a-zA-Z_.]+)");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The constant cacheTag.
|
|
||||||
*/
|
|
||||||
// ========= 缓存 ========
|
|
||||||
public volatile CompoundTag cacheTag = null;
|
|
||||||
/**
|
|
||||||
* The constant cacheHash.
|
|
||||||
*/
|
|
||||||
public volatile int cacheHash = -1;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final Map<String, String> servers = new TreeMap<>();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private boolean disabledJoinQuitMessage = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析服务器配置,支持带参数的翻译键
|
|
||||||
*
|
|
||||||
* @param servers 配置列表
|
|
||||||
* @return 不可修改的服务器映射
|
|
||||||
*/
|
|
||||||
private @NotNull @Unmodifiable Map<String, String> parseServer(@NotNull List<? extends String> servers) {
|
|
||||||
Map<String, String> serverMap = new TreeMap<>();
|
|
||||||
for (String server : servers) {
|
|
||||||
Matcher matcher = SYNTAX.matcher(server);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
String key = matcher.group(1).trim(); // 服务器ID
|
|
||||||
String value = matcher.group(2).trim(); // 翻译键或直接文本
|
|
||||||
|
|
||||||
if (!serverMap.containsKey(key)) {
|
|
||||||
// 验证并规范化值
|
|
||||||
String normalizedValue = normalizeValue(value);
|
|
||||||
serverMap.put(key, normalizedValue);
|
|
||||||
log.debug("Loaded server: {} -> {}", key, normalizedValue);
|
|
||||||
} else {
|
|
||||||
log.warn("Duplicate server name '{}' found in config, skip it", key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn("Invalid server entry format: '{}', expected: 'server_name: translate_key'", server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableMap(serverMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规范化值,确保格式正确
|
|
||||||
* 支持:
|
|
||||||
* 1. 纯翻译键: "ltd.mod.client.menu.button.hub"
|
|
||||||
* 2. 带参数的翻译键: "ltd.mod.client.menu.button.hub?player={player}&count={playerCount}"
|
|
||||||
* 3. 直接文本: "🏠 Hub Server"
|
|
||||||
* 4. 带格式的文本: "§a[Hub] §fServer"
|
|
||||||
*/
|
|
||||||
private String normalizeValue(String value) {
|
|
||||||
if (value == null || value.trim().isEmpty()) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = value.trim();
|
|
||||||
|
|
||||||
// 检查是否是翻译键格式 (包含字母、数字、下划线、点)
|
|
||||||
Matcher keyMatcher = KEY_PATTERN.matcher(value);
|
|
||||||
if (keyMatcher.find()) {
|
|
||||||
String potentialKey = keyMatcher.group(1);
|
|
||||||
// 如果匹配到的是翻译键格式,保留原样
|
|
||||||
if (potentialKey.matches("^[a-zA-Z_][a-zA-Z0-9_.]*$")) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果不是翻译键格式,作为直接文本处理
|
|
||||||
// 检查是否包含参数
|
|
||||||
if (value.contains("?")) {
|
|
||||||
// 可能是格式: "text?param=value"
|
|
||||||
String[] parts = value.split("\\?", 2);
|
|
||||||
String prefix = parts[0];
|
|
||||||
// 如果前缀看起来像翻译键,保留;否则作为文本
|
|
||||||
if (prefix.matches("^[a-zA-Z_][a-zA-Z0-9_.]*$")) {
|
|
||||||
return value; // 保留完整的翻译键+参数格式
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认返回原值
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取带参数的翻译键
|
|
||||||
* 如果值是翻译键格式,返回原值;否则返回null
|
|
||||||
*/
|
|
||||||
public String getTranslateKey(String serverId) {
|
|
||||||
String value = servers.get(serverId);
|
|
||||||
if (value == null) return null;
|
|
||||||
|
|
||||||
// 检查是否是翻译键格式(包含字母、数字、下划线、点)
|
|
||||||
if (value.matches("^[a-zA-Z_][a-zA-Z0-9_.]*\\??.*$")) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取直接文本
|
|
||||||
* 如果值不是翻译键格式,返回原值;否则返回null
|
|
||||||
*/
|
|
||||||
public String getDirectText(String serverId) {
|
|
||||||
String value = servers.get(serverId);
|
|
||||||
if (value == null) return null;
|
|
||||||
|
|
||||||
// 如果不是翻译键格式,作为直接文本
|
|
||||||
if (!value.matches("^[a-zA-Z_][a-zA-Z0-9_.]*\\??.*$")) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查服务器是否使用翻译键
|
|
||||||
*/
|
|
||||||
public boolean isTranslateKey(String serverId) {
|
|
||||||
return getTranslateKey(serverId) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器显示文本(用于搜索)
|
|
||||||
*/
|
|
||||||
public String getDisplayText(String serverId) {
|
|
||||||
String value = servers.get(serverId);
|
|
||||||
if (value == null) return serverId;
|
|
||||||
|
|
||||||
// 如果是翻译键,返回翻译键本身(将在ServerSelectionList中解析)
|
|
||||||
if (value.matches("^[a-zA-Z_][a-zA-Z0-9_.]*\\??.*$")) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
// 否则返回直接文本
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reloadAll() {
|
|
||||||
try {
|
|
||||||
clear();
|
|
||||||
servers.putAll(parseServer(CrossServerConfig.SERVER_LIST.get()));
|
|
||||||
disabledJoinQuitMessage = CrossServerConfig.DISABLED_JOIN_QUIT_MESSAGE.get();
|
|
||||||
cacheHash = -1;
|
|
||||||
cacheTag = serializeToNBT();
|
|
||||||
log.info("Configs reloaded, loaded {} servers", servers.size());
|
|
||||||
|
|
||||||
// 打印加载的服务器列表(用于调试)
|
|
||||||
if (!servers.isEmpty()) {
|
|
||||||
log.debug("Loaded servers:");
|
|
||||||
servers.forEach((id, value) ->
|
|
||||||
log.debug(" {} -> {}", id, value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to reload configs", e);
|
|
||||||
cacheHash = -1;
|
|
||||||
cacheTag = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear.
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
servers.clear();
|
|
||||||
cacheHash = -1;
|
|
||||||
cacheTag = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized CompoundTag serializeToNBT() {
|
|
||||||
int currentHash = calculateConfigHash();
|
|
||||||
if (cacheTag != null && cacheHash == currentHash) {
|
|
||||||
return cacheTag;
|
|
||||||
}
|
|
||||||
CompoundTag tag = new CompoundTag();
|
|
||||||
serializeMap(tag, "servers", this.servers);
|
|
||||||
tag.putBoolean("disabledJoinQuitMessage", disabledJoinQuitMessage);
|
|
||||||
cacheHash = calculateConfigHash();
|
|
||||||
cacheTag = tag;
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void serializeMap(CompoundTag parent, String key, @NotNull Map<String, String> map) {
|
|
||||||
CompoundTag mapTag = new CompoundTag();
|
|
||||||
TreeMap<String, String> sortedMap = new TreeMap<>(map);
|
|
||||||
for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
|
|
||||||
mapTag.putString(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
parent.put(key, mapTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从NBT反序列化配置管理器状态
|
|
||||||
*
|
|
||||||
* @param tag the tag
|
|
||||||
*/
|
|
||||||
public void deserializeFromNBT(@NotNull CompoundTag tag) {
|
|
||||||
cacheHash = -1;
|
|
||||||
cacheTag = null;
|
|
||||||
clear();
|
|
||||||
deserializeMap(tag, "servers", servers);
|
|
||||||
if (tag.contains("disabledJoinQuitMessage")) {
|
|
||||||
disabledJoinQuitMessage = tag.getBoolean("disabledJoinQuitMessage");
|
|
||||||
}
|
|
||||||
cacheTag = serializeToNBT();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deserializeMap(@NotNull CompoundTag parent, String key, Map<String, String> map) {
|
|
||||||
if (parent.contains(key)) {
|
|
||||||
CompoundTag mapTag = parent.getCompound(key);
|
|
||||||
TreeMap<String, String> tempMap = new TreeMap<>();
|
|
||||||
for (String key_ : mapTag.getAllKeys()) {
|
|
||||||
tempMap.put(key_, mapTag.getString(key_));
|
|
||||||
}
|
|
||||||
map.clear();
|
|
||||||
map.putAll(tempMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loading.
|
|
||||||
*
|
|
||||||
* @param manager the manager
|
|
||||||
*/
|
|
||||||
public static void loading(@NotNull CrossServerConfigManager manager) {
|
|
||||||
manager.reloadAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloading.
|
|
||||||
*
|
|
||||||
* @param manager the manager
|
|
||||||
*/
|
|
||||||
public static void reloading(@NotNull CrossServerConfigManager manager) {
|
|
||||||
manager.reloadAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unloading.
|
|
||||||
*
|
|
||||||
* @param manager the manager
|
|
||||||
*/
|
|
||||||
public static void unloading(CrossServerConfigManager manager) {
|
|
||||||
if(manager != null) manager.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int calculateConfigHash() {
|
|
||||||
// 使用FNV-1a哈希算法
|
|
||||||
int hash = 0x811c9dc5; // FNV偏移基础值
|
|
||||||
TreeMap<String, String> sortedMap = new TreeMap<>(servers);
|
|
||||||
hash = fnv1aHashMap(hash, sortedMap);
|
|
||||||
// 包含 disabledJoinQuitMessage 在哈希中
|
|
||||||
hash = fnv1aHashString(hash, String.valueOf(disabledJoinQuitMessage));
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int fnv1aHashString(int hash, @NotNull String str) {
|
|
||||||
for (int i = 0; i < str.length(); i++) {
|
|
||||||
hash ^= str.charAt(i);
|
|
||||||
hash *= 0x01000193;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int fnv1aHashMap(int hash, @NotNull Map<String, String> map) {
|
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
|
||||||
hash = fnv1aHashString(hash, entry.getKey());
|
|
||||||
hash = fnv1aHashString(hash, entry.getValue());
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broad hash packet.
|
|
||||||
*/
|
|
||||||
public void broadHashPacket() {
|
|
||||||
if (cacheHash != -1){
|
|
||||||
NetworkHandler.sendToAllPlayer(new CommonConfigHashInformPacket(cacheHash));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器数量
|
|
||||||
*/
|
|
||||||
public int getServerCount() {
|
|
||||||
return servers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查服务器是否存在于配置中
|
|
||||||
*/
|
|
||||||
public boolean hasServer(String serverId) {
|
|
||||||
return servers.containsKey(serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器配置值
|
|
||||||
*/
|
|
||||||
public String getServerValue(String serverId) {
|
|
||||||
return servers.get(serverId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,7 +20,7 @@ public class CrossServerModMixinPlugin implements IMixinConfigPlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldApplyMixin(String s, String s1) {
|
public boolean shouldApplyMixin(String s, String s1) {
|
||||||
return true;
|
return !FMLEnvironment.dist.isDedicatedServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.mixin;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
|
|
||||||
import com.leisuretimedock.crossmod.util.ModLogger;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraft.network.PacketListener;
|
|
||||||
import net.minecraft.network.protocol.BundlePacket;
|
|
||||||
import net.minecraft.network.protocol.Packet;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
@Mixin(BundlePacket.class)
|
|
||||||
public abstract class MixinBundlePacket<T extends PacketListener> implements Packet<T> {
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
public abstract Iterable<Packet<T>> subPackets();
|
|
||||||
|
|
||||||
@Unique
|
|
||||||
private static int encodeCallCount = 0;
|
|
||||||
|
|
||||||
@Unique
|
|
||||||
private int savedWriterIndex = -1;
|
|
||||||
|
|
||||||
@Unique
|
|
||||||
private int savedReaderIndex = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 监控 write 方法开始 - 记录初始状态
|
|
||||||
*/
|
|
||||||
@Inject(method = "write", at = @At("HEAD"), cancellable = false)
|
|
||||||
private void onWriteHead(FriendlyByteBuf buffer, CallbackInfo ci) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
encodeCallCount++;
|
|
||||||
savedWriterIndex = buffer.writerIndex();
|
|
||||||
savedReaderIndex = buffer.readerIndex();
|
|
||||||
|
|
||||||
ModLogger.bundle("");
|
|
||||||
ModLogger.bundle("========== BundlePacket.write #{} ==========", encodeCallCount);
|
|
||||||
ModLogger.bundle("BundlePacket type: {}", this.getClass().getSimpleName());
|
|
||||||
ModLogger.bundle("Buffer state BEFORE write:");
|
|
||||||
ModLogger.bundle(" readerIndex: {}", savedReaderIndex);
|
|
||||||
ModLogger.bundle(" writerIndex: {}", savedWriterIndex);
|
|
||||||
ModLogger.bundle(" readableBytes: {}", buffer.readableBytes());
|
|
||||||
ModLogger.bundle(" writableBytes: {}", buffer.writableBytes());
|
|
||||||
|
|
||||||
// 记录子包信息
|
|
||||||
int packetCount = 0;
|
|
||||||
ModLogger.bundle("Sub-packets in this bundle:");
|
|
||||||
for (Packet<T> packet : subPackets()) {
|
|
||||||
packetCount++;
|
|
||||||
ModLogger.bundle(" #{}. {}", packetCount, packet.getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
ModLogger.bundle("Total sub-packets: {}", packetCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 监控 write 方法返回后 - 检查是否有异常写入
|
|
||||||
*/
|
|
||||||
@Inject(method = "write", at = @At("RETURN"))
|
|
||||||
private void onWriteReturn(FriendlyByteBuf buffer, CallbackInfo ci) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int afterWriterIndex = buffer.writerIndex();
|
|
||||||
int afterReaderIndex = buffer.readerIndex();
|
|
||||||
int bytesWritten = afterWriterIndex - savedWriterIndex;
|
|
||||||
int readerMoved = afterReaderIndex - savedReaderIndex;
|
|
||||||
|
|
||||||
ModLogger.bundle("Buffer state AFTER write:");
|
|
||||||
ModLogger.bundle(" readerIndex: {} (changed by {})", afterReaderIndex, readerMoved);
|
|
||||||
ModLogger.bundle(" writerIndex: {} (changed by {})", afterWriterIndex, bytesWritten);
|
|
||||||
ModLogger.bundle(" readableBytes: {}", buffer.readableBytes());
|
|
||||||
|
|
||||||
// 关键检查:BundlePacket.write() 应该是空实现
|
|
||||||
// 正常情况下不应该有任何写入操作
|
|
||||||
if (bytesWritten != 0) {
|
|
||||||
ModLogger.error("!!! CRITICAL: BundlePacket.write wrote {} bytes to buffer !!!", bytesWritten);
|
|
||||||
ModLogger.error("BundlePacket.write() should be EMPTY and write NOTHING!");
|
|
||||||
ModLogger.error("This indicates a bug in: {}", this.getClass().getSimpleName());
|
|
||||||
|
|
||||||
// 如果写入了数据,尝试分析写入的内容
|
|
||||||
if (bytesWritten > 0 && bytesWritten <= buffer.writableBytes()) {
|
|
||||||
int savedIdx = buffer.readerIndex();
|
|
||||||
int readPos = savedWriterIndex;
|
|
||||||
buffer.readerIndex(readPos);
|
|
||||||
|
|
||||||
int showBytes = Math.min(bytesWritten, 64);
|
|
||||||
byte[] writtenData = new byte[showBytes];
|
|
||||||
buffer.readBytes(writtenData);
|
|
||||||
|
|
||||||
StringBuilder hex = new StringBuilder();
|
|
||||||
StringBuilder ascii = new StringBuilder();
|
|
||||||
for (int i = 0; i < showBytes; i++) {
|
|
||||||
hex.append(String.format("%02X ", writtenData[i] & 0xFF));
|
|
||||||
char c = (char) (writtenData[i] & 0xFF);
|
|
||||||
ascii.append(c >= 32 && c < 127 ? c : '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
ModLogger.bundle("Written data hex (first {} of {}):", showBytes, bytesWritten);
|
|
||||||
ModLogger.bundle(" {}", hex.toString());
|
|
||||||
ModLogger.bundle("Written data ASCII: {}", ascii.toString());
|
|
||||||
buffer.readerIndex(savedIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印堆栈,找出是谁调用了写入
|
|
||||||
ModLogger.bundle("Stack trace of the write operation:");
|
|
||||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
|
||||||
for (int i = 2; i < Math.min(stack.length, 12); i++) {
|
|
||||||
ModLogger.bundle(" at {}", stack[i].toString());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ModLogger.bundle("✓ BundlePacket.write wrote 0 bytes (correct behavior)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查读取位置是否被意外移动
|
|
||||||
if (readerMoved != 0) {
|
|
||||||
ModLogger.warn("WARNING: BundlePacket.write changed readerIndex by {} bytes!", readerMoved);
|
|
||||||
ModLogger.warn("This should not happen in write operation!");
|
|
||||||
}
|
|
||||||
|
|
||||||
ModLogger.bundle("========================================");
|
|
||||||
|
|
||||||
// 重置保存的索引
|
|
||||||
savedWriterIndex = -1;
|
|
||||||
savedReaderIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可选:监控子包的写入过程(更细粒度的调试)
|
|
||||||
* 需要在配置中开启更详细的调试
|
|
||||||
*/
|
|
||||||
@Unique
|
|
||||||
protected void debugSubPacketWrite(Packet<T> packet, FriendlyByteBuf buffer, int beforeWrite) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CrossCommonModConfig.getInstance().isDebugPackets()) {
|
|
||||||
int afterWrite = buffer.writerIndex();
|
|
||||||
int subPacketBytes = afterWrite - beforeWrite;
|
|
||||||
ModLogger.debug(" Sub-packet {} wrote {} bytes",
|
|
||||||
packet.getClass().getSimpleName(), subPacketBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.mixin;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
|
|
||||||
import com.leisuretimedock.crossmod.util.ModLogger;
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import net.minecraft.network.Connection;
|
|
||||||
import net.minecraft.network.PacketSendListener;
|
|
||||||
import net.minecraft.network.protocol.BundleDelimiterPacket;
|
|
||||||
import net.minecraft.network.protocol.Packet;
|
|
||||||
import net.minecraft.network.protocol.game.ClientboundBundlePacket;
|
|
||||||
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(Connection.class)
|
|
||||||
public class MixinConnection {
|
|
||||||
@Shadow
|
|
||||||
private Channel channel;
|
|
||||||
|
|
||||||
private static int sendCount = 0;
|
|
||||||
|
|
||||||
@Inject(method = "send(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketSendListener;)V",
|
|
||||||
at = @At("HEAD"))
|
|
||||||
private void onSendPacket(Packet<?> packet, PacketSendListener sendListener, CallbackInfo ci) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet instanceof BundleDelimiterPacket) {
|
|
||||||
sendCount++;
|
|
||||||
ModLogger.bundle("");
|
|
||||||
ModLogger.bundle(">>> Sending BundleDelimiterPacket #{}", sendCount);
|
|
||||||
ModLogger.bundle("Channel: {}", channel);
|
|
||||||
logStackTrace();
|
|
||||||
} else if (packet instanceof ClientboundBundlePacket) {
|
|
||||||
ModLogger.bundle(">>> Sending ClientboundBundlePacket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logStackTrace() {
|
|
||||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
|
||||||
for (int i = 2; i < Math.min(stack.length, 8); i++) {
|
|
||||||
ModLogger.bundle(" at {}", stack[i].toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
package com.leisuretimedock.crossmod.mixin;
|
package com.leisuretimedock.crossmod.mixin;
|
||||||
|
|
||||||
import icyllis.modernui.mc.forge.NetworkHandler;
|
import icyllis.modernui.mc.forge.NetworkHandler;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Pseudo;
|
import org.spongepowered.asm.mixin.Pseudo;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
|
||||||
@Pseudo
|
@Pseudo
|
||||||
@Mixin(value = NetworkHandler.class, remap = false)
|
@Mixin(value = NetworkHandler.class, remap = false)
|
||||||
public class MixinMUINetWorkHandler {
|
public class MixinMUINetWorkHandler {
|
||||||
|
/**
|
||||||
@ModifyVariable(
|
* 修补构造 ResourceLocation("modernui", id) 时,若 id 是空字符串,则替换为 "default"
|
||||||
|
*/
|
||||||
|
@ModifyArg(
|
||||||
method = "<init>",
|
method = "<init>",
|
||||||
at = @At("HEAD"),
|
at = @At(
|
||||||
argsOnly = true,
|
value = "INVOKE",
|
||||||
ordinal = 0
|
target = "Lnet/minecraft/resources/ResourceLocation;<init>(Ljava/lang/String;Ljava/lang/String;)V"
|
||||||
|
),
|
||||||
|
index = 1 // 修改 id 参数
|
||||||
)
|
)
|
||||||
private static ResourceLocation modifyNameParameter(@NotNull ResourceLocation name) {
|
private String fixEmptyId(String id) {
|
||||||
return name.getPath().isEmpty() ? name : new ResourceLocation(name.getNamespace(), "default");
|
return id == null || id.isEmpty() ? "default" : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.mixin;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
|
|
||||||
import com.leisuretimedock.crossmod.util.ModLogger;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import net.minecraft.network.PacketDecoder;
|
|
||||||
import net.minecraft.network.protocol.BundleDelimiterPacket;
|
|
||||||
import net.minecraft.network.protocol.game.ClientboundBundlePacket;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Mixin(PacketDecoder.class)
|
|
||||||
public class MixinPacketDecoder {
|
|
||||||
|
|
||||||
private static int decodeCount = 0;
|
|
||||||
private static int bundleDelimiterCount = 0;
|
|
||||||
private static int clientboundBundleCount = 0;
|
|
||||||
|
|
||||||
@Inject(method = "decode", at = @At("HEAD"))
|
|
||||||
private void onDecodeHead(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, CallbackInfo ci) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in.readableBytes() < 1) return;
|
|
||||||
|
|
||||||
decodeCount++;
|
|
||||||
int savedIdx = in.readerIndex();
|
|
||||||
int packetId = in.getByte(savedIdx) & 0xFF;
|
|
||||||
|
|
||||||
ModLogger.bundle("");
|
|
||||||
ModLogger.bundle("========== PacketDecoder.decode #{} ==========", decodeCount);
|
|
||||||
ModLogger.bundle("Packet ID: 0x{} ({})", Integer.toHexString(packetId), packetId);
|
|
||||||
ModLogger.bundle("Available bytes before decode: {}", in.readableBytes());
|
|
||||||
ModLogger.bundle("Reader index: {}, Writer index: {}", in.readerIndex(), in.writerIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(method = "decode", at = @At("RETURN"))
|
|
||||||
private void onDecodeReturn(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, CallbackInfo ci) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查解码出的包类型
|
|
||||||
boolean isBundleDelimiter = false;
|
|
||||||
boolean isClientboundBundle = false;
|
|
||||||
|
|
||||||
for (Object packet : out) {
|
|
||||||
if (packet instanceof BundleDelimiterPacket) {
|
|
||||||
isBundleDelimiter = true;
|
|
||||||
bundleDelimiterCount++;
|
|
||||||
} else if (packet instanceof ClientboundBundlePacket) {
|
|
||||||
isClientboundBundle = true;
|
|
||||||
clientboundBundleCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int remaining = in.readableBytes();
|
|
||||||
|
|
||||||
if (isBundleDelimiter) {
|
|
||||||
ModLogger.bundle("");
|
|
||||||
ModLogger.bundle(">>> Decoded BundleDelimiterPacket #{} <<<", bundleDelimiterCount);
|
|
||||||
ModLogger.bundle("Remaining bytes after decode: {}", remaining);
|
|
||||||
|
|
||||||
if (remaining > 0) {
|
|
||||||
ModLogger.error("!!! CRITICAL: {} bytes remain unread after BundleDelimiterPacket !!!", remaining);
|
|
||||||
logRemainingBytes(in, remaining);
|
|
||||||
ModLogger.bundle("Stack trace:");
|
|
||||||
logStackTrace();
|
|
||||||
} else {
|
|
||||||
ModLogger.bundle("✓ BundleDelimiterPacket decoded cleanly");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isClientboundBundle) {
|
|
||||||
ModLogger.bundle("");
|
|
||||||
ModLogger.bundle(">>> Decoded ClientboundBundlePacket #{} <<<", clientboundBundleCount);
|
|
||||||
ModLogger.bundle("Remaining bytes after decode: {}", remaining);
|
|
||||||
|
|
||||||
if (remaining > 0) {
|
|
||||||
ModLogger.error("!!! CRITICAL: {} bytes remain unread after ClientboundBundlePacket !!!", remaining);
|
|
||||||
logRemainingBytes(in, remaining);
|
|
||||||
} else {
|
|
||||||
ModLogger.bundle("✓ ClientboundBundlePacket decoded cleanly");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logRemainingBytes(ByteBuf in, int remaining) {
|
|
||||||
int savedIdx = in.readerIndex();
|
|
||||||
int showBytes = Math.min(remaining, 64);
|
|
||||||
byte[] leftover = new byte[showBytes];
|
|
||||||
in.readBytes(leftover);
|
|
||||||
|
|
||||||
StringBuilder hex = new StringBuilder();
|
|
||||||
StringBuilder ascii = new StringBuilder();
|
|
||||||
for (int i = 0; i < showBytes; i++) {
|
|
||||||
hex.append(String.format("%02X ", leftover[i] & 0xFF));
|
|
||||||
char c = (char) (leftover[i] & 0xFF);
|
|
||||||
ascii.append(c >= 32 && c < 127 ? c : '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
ModLogger.bundle("Leftover data hex (first {} of {}): {}", showBytes, remaining, hex.toString());
|
|
||||||
ModLogger.bundle("Leftover data ASCII: {}", ascii.toString());
|
|
||||||
|
|
||||||
// 尝试识别可能的包类型
|
|
||||||
if (showBytes >= 1) {
|
|
||||||
int nextPacketId = leftover[0] & 0xFF;
|
|
||||||
ModLogger.bundle("Next packet ID would be: 0x{} ({})",
|
|
||||||
Integer.toHexString(nextPacketId), nextPacketId);
|
|
||||||
}
|
|
||||||
|
|
||||||
in.readerIndex(savedIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logStackTrace() {
|
|
||||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
|
||||||
for (int i = 2; i < Math.min(stack.length, 10); i++) {
|
|
||||||
ModLogger.bundle(" at {}", stack[i].toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.mixin;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
|
||||||
import net.minecraft.server.players.PlayerList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
@Mixin(PlayerList.class)
|
|
||||||
public class MixinPlayerList {
|
|
||||||
@Shadow
|
|
||||||
@Final
|
|
||||||
private static Logger LOGGER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截双参数版本
|
|
||||||
*/
|
|
||||||
@Inject(
|
|
||||||
method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Z)V",
|
|
||||||
at = @At("HEAD"),
|
|
||||||
cancellable = true
|
|
||||||
)
|
|
||||||
private void onBroadcastSystemMessage(Component message, boolean bypassHiddenChat, CallbackInfo ci) {
|
|
||||||
if (crossServerTeleport$shouldCancel(message)) {
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截三参数版本
|
|
||||||
*/
|
|
||||||
@Inject(
|
|
||||||
method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Ljava/util/function/Function;Z)V",
|
|
||||||
at = @At("HEAD"),
|
|
||||||
cancellable = true
|
|
||||||
)
|
|
||||||
private void onBroadcastSystemMessage(Component serverMessage, Function<ServerPlayer, Component> playerMessageFactory, boolean bypassHiddenChat, CallbackInfo ci) {
|
|
||||||
if (crossServerTeleport$shouldCancel(serverMessage)) {
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Unique
|
|
||||||
private boolean crossServerTeleport$shouldCancel(Component message) {
|
|
||||||
if (!CrossServerConfigManager.INSTANCE.isDisabledJoinQuitMessage()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 TranslatableContents 的翻译键
|
|
||||||
if (message.getContents() instanceof TranslatableContents translatable) {
|
|
||||||
String key = translatable.getKey();
|
|
||||||
if ("multiplayer.player.joined".equals(key) ||
|
|
||||||
"multiplayer.player.joined.renamed".equals(key) ||
|
|
||||||
"multiplayer.player.left".equals(key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
// 客户端网络处理类(CrossMod 端)
|
||||||
package com.leisuretimedock.crossmod.network;
|
package com.leisuretimedock.crossmod.network;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
import com.leisuretimedock.crossmod.network.toClient.*;
|
import com.leisuretimedock.crossmod.network.toClient.PingMessagePayload;
|
||||||
|
import com.leisuretimedock.crossmod.network.toClient.PingResultPacket;
|
||||||
|
import com.leisuretimedock.crossmod.network.toClient.PingStatsPacket;
|
||||||
import com.leisuretimedock.crossmod.network.toServer.PongMessagePayload;
|
import com.leisuretimedock.crossmod.network.toServer.PongMessagePayload;
|
||||||
import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket;
|
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
@ -15,7 +17,6 @@ import net.minecraftforge.network.NetworkDirection;
|
||||||
import net.minecraftforge.network.NetworkRegistry;
|
import net.minecraftforge.network.NetworkRegistry;
|
||||||
import net.minecraftforge.network.PacketDistributor;
|
import net.minecraftforge.network.PacketDistributor;
|
||||||
import net.minecraftforge.network.simple.SimpleChannel;
|
import net.minecraftforge.network.simple.SimpleChannel;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
@ -73,26 +74,6 @@ public class NetworkHandler {
|
||||||
PingStatsPacket::decode,
|
PingStatsPacket::decode,
|
||||||
PingStatsPacket::handle
|
PingStatsPacket::handle
|
||||||
);
|
);
|
||||||
CHANNEL.messageBuilder(SyncCommonConfigPacket.class, messageId++, NetworkDirection.PLAY_TO_CLIENT)
|
|
||||||
.decoder(SyncCommonConfigPacket::decode)
|
|
||||||
.encoder(SyncCommonConfigPacket::encode)
|
|
||||||
.consumerNetworkThread(SyncCommonConfigPacket::handle)
|
|
||||||
.add();
|
|
||||||
CHANNEL.messageBuilder(SyncCommonConfigRequestPacket.class, messageId++, NetworkDirection.PLAY_TO_SERVER)
|
|
||||||
.decoder(SyncCommonConfigRequestPacket::decode)
|
|
||||||
.encoder(SyncCommonConfigRequestPacket::encode)
|
|
||||||
.consumerNetworkThread(SyncCommonConfigRequestPacket::handle)
|
|
||||||
.add();
|
|
||||||
CHANNEL.messageBuilder(CommonConfigHashInformPacket.class, messageId++, NetworkDirection.PLAY_TO_CLIENT)
|
|
||||||
.decoder(CommonConfigHashInformPacket::decode)
|
|
||||||
.encoder(CommonConfigHashInformPacket::encode)
|
|
||||||
.consumerNetworkThread(CommonConfigHashInformPacket::handle)
|
|
||||||
.add();
|
|
||||||
CHANNEL.messageBuilder(GotoServerPayload.class, messageId++, NetworkDirection.PLAY_TO_CLIENT)
|
|
||||||
.decoder(GotoServerPayload::decode)
|
|
||||||
.encoder(GotoServerPayload::encode)
|
|
||||||
.consumerMainThread(GotoServerPayload::handle)
|
|
||||||
.add();
|
|
||||||
}
|
}
|
||||||
// 新增发送报告方法
|
// 新增发送报告方法
|
||||||
public static void sendPingReport(ServerPlayer player,
|
public static void sendPingReport(ServerPlayer player,
|
||||||
|
|
@ -107,7 +88,7 @@ public class NetworkHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void sendPingResults(ServerPlayer player, @NotNull Map<UUID, Long> results) {
|
public static void sendPingResults(ServerPlayer player, Map<UUID, Long> results) {
|
||||||
// 创建平均时延映射
|
// 创建平均时延映射
|
||||||
Map<UUID, Double> averageLatencies = new HashMap<>();
|
Map<UUID, Double> averageLatencies = new HashMap<>();
|
||||||
|
|
||||||
|
|
@ -135,7 +116,7 @@ public class NetworkHandler {
|
||||||
public static void sendPingRequest(ServerPlayer player, UUID requestId) {
|
public static void sendPingRequest(ServerPlayer player, UUID requestId) {
|
||||||
try {
|
try {
|
||||||
CHANNEL.sendTo(new PingMessagePayload(requestId),
|
CHANNEL.sendTo(new PingMessagePayload(requestId),
|
||||||
player.connection.connection,
|
player.connection.getConnection(),
|
||||||
NetworkDirection.PLAY_TO_CLIENT);
|
NetworkDirection.PLAY_TO_CLIENT);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("发送ping请求失败", e);
|
log.error("发送ping请求失败", e);
|
||||||
|
|
@ -148,7 +129,7 @@ public class NetworkHandler {
|
||||||
* @param subChannel 子通道标识
|
* @param subChannel 子通道标识
|
||||||
* @param payload 负载数据(字节数组)
|
* @param payload 负载数据(字节数组)
|
||||||
*/
|
*/
|
||||||
public static void sendPluginMessage(ResourceLocation subChannel, byte @NotNull [] payload) {
|
public static void sendPluginMessage(ResourceLocation subChannel, byte[] payload) {
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(payload.length));
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(payload.length));
|
||||||
buf.writeBytes(payload);
|
buf.writeBytes(payload);
|
||||||
|
|
||||||
|
|
@ -171,37 +152,4 @@ public class NetworkHandler {
|
||||||
public static void sendTeleportRequest(String serverName) {
|
public static void sendTeleportRequest(String serverName) {
|
||||||
PluginMessageListener.sendTeleport(serverName);
|
PluginMessageListener.sendTeleport(serverName);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Send to player.
|
|
||||||
*
|
|
||||||
* @param <MSG> the type parameter
|
|
||||||
* @param message the message
|
|
||||||
* @param player the player
|
|
||||||
*/
|
|
||||||
public static <MSG> void sendToPlayer(MSG message, ServerPlayer player){
|
|
||||||
CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send to all player.
|
|
||||||
*
|
|
||||||
* @param <MSG> the type parameter
|
|
||||||
* @param message the message
|
|
||||||
*/
|
|
||||||
public static <MSG> void sendToAllPlayer(MSG message){
|
|
||||||
CHANNEL.send(PacketDistributor.ALL.noArg(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send to player.
|
|
||||||
*
|
|
||||||
* @param <MSG> the type parameter
|
|
||||||
* @param <T> the type parameter
|
|
||||||
* @param message the message
|
|
||||||
* @param entity the entity
|
|
||||||
* @param packetDistributor the packet distributor
|
|
||||||
*/
|
|
||||||
public static <MSG, T> void sendToPlayer(MSG message, T entity, @NotNull PacketDistributor<T> packetDistributor){
|
|
||||||
CHANNEL.send(packetDistributor.with(() -> entity), message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,15 +3,11 @@ package com.leisuretimedock.crossmod.network;
|
||||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class PingRequestManager {
|
public final class PingRequestManager {
|
||||||
// 配置常量
|
// 配置常量
|
||||||
|
|
@ -108,8 +104,8 @@ public final class PingRequestManager {
|
||||||
|
|
||||||
// 网络拥塞检测
|
// 网络拥塞检测
|
||||||
if (ping > DEFAULT_TIMEOUT_MS * 0.8) {
|
if (ping > DEFAULT_TIMEOUT_MS * 0.8) {
|
||||||
player.displayClientMessage(Component.translatable("ltd.mod.ping.warn.network_latency", player.getUUID()),
|
player.sendMessage(new TranslatableComponent("ltd.mod.ping.warn.network_latency"),
|
||||||
true);
|
player.getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePingHistory(data, ping);
|
updatePingHistory(data, ping);
|
||||||
|
|
@ -179,7 +175,7 @@ public final class PingRequestManager {
|
||||||
int successfulRequests = 0;
|
int successfulRequests = 0;
|
||||||
|
|
||||||
for (PlayerPingData data : playerData.values()) {
|
for (PlayerPingData data : playerData.values()) {
|
||||||
synchronized (playerData) {
|
synchronized (data) {
|
||||||
allPings.addAll(data.pingHistory);
|
allPings.addAll(data.pingHistory);
|
||||||
totalRequests += data.totalRequests;
|
totalRequests += data.totalRequests;
|
||||||
successfulRequests += data.successfulRequests;
|
successfulRequests += data.successfulRequests;
|
||||||
|
|
@ -212,11 +208,11 @@ public final class PingRequestManager {
|
||||||
/**
|
/**
|
||||||
* 获取所有玩家的最新ping值
|
* 获取所有玩家的最新ping值
|
||||||
*/
|
*/
|
||||||
public static @NotNull Map<UUID, Long> getAllLatestPings() {
|
public static Map<UUID, Long> getAllLatestPings() {
|
||||||
Map<UUID, Long> results = new HashMap<>();
|
Map<UUID, Long> results = new HashMap<>();
|
||||||
|
|
||||||
playerData.forEach((uuid, data) -> {
|
playerData.forEach((uuid, data) -> {
|
||||||
synchronized (PingRequestManager.class) {
|
synchronized (data) {
|
||||||
if (!data.pingHistory.isEmpty()) {
|
if (!data.pingHistory.isEmpty()) {
|
||||||
results.put(uuid, data.pingHistory.getLast());
|
results.put(uuid, data.pingHistory.getLast());
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +226,7 @@ public final class PingRequestManager {
|
||||||
* @param players 要查询的玩家集合
|
* @param players 要查询的玩家集合
|
||||||
* @return 包含玩家UUID和最新ping时间的Map
|
* @return 包含玩家UUID和最新ping时间的Map
|
||||||
*/
|
*/
|
||||||
public static @NotNull Map<UUID, Long> getLatestPingsForPlayers(@NotNull Collection<ServerPlayer> players) {
|
public static Map<UUID, Long> getLatestPingsForPlayers(Collection<ServerPlayer> players) {
|
||||||
Map<UUID, Long> results = new HashMap<>();
|
Map<UUID, Long> results = new HashMap<>();
|
||||||
|
|
||||||
for (ServerPlayer player : players) {
|
for (ServerPlayer player : players) {
|
||||||
|
|
@ -285,12 +281,12 @@ public final class PingRequestManager {
|
||||||
|
|
||||||
if (attempt == count) {
|
if (attempt == count) {
|
||||||
data.batchInProgress = 0;
|
data.batchInProgress = 0;
|
||||||
player.displayClientMessage(
|
player.sendMessage(
|
||||||
Component.translatable("ltd.mod.ping.success.multiping.complete", count),
|
new TranslatableComponent("ltd.mod.ping.success.multiping.complete", count),
|
||||||
true);
|
player.getUUID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, i * intervalMs, TimeUnit.MILLISECONDS);
|
}, i * Math.max(intervalMs, MIN_PING_INTERVAL), TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -299,7 +295,7 @@ public final class PingRequestManager {
|
||||||
/**
|
/**
|
||||||
* 取消该玩家的所有进行中的Ping请求
|
* 取消该玩家的所有进行中的Ping请求
|
||||||
*/
|
*/
|
||||||
public static void cancelPings(@NotNull ServerPlayer player) {
|
public static void cancelPings(ServerPlayer player) {
|
||||||
PlayerPingData data = playerData.get(player.getUUID());
|
PlayerPingData data = playerData.get(player.getUUID());
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
synchronized (data) {
|
synchronized (data) {
|
||||||
|
|
@ -332,7 +328,7 @@ public final class PingRequestManager {
|
||||||
Map<UUID, Double> averages = new HashMap<>();
|
Map<UUID, Double> averages = new HashMap<>();
|
||||||
|
|
||||||
playerData.forEach((uuid, data) -> {
|
playerData.forEach((uuid, data) -> {
|
||||||
synchronized (PingRequestManager.class) {
|
synchronized (data) {
|
||||||
if (!data.pingHistory.isEmpty()) {
|
if (!data.pingHistory.isEmpty()) {
|
||||||
latestPings.put(uuid, PingRequestManager.getLatestPing(uuid).orElse(-1L));
|
latestPings.put(uuid, PingRequestManager.getLatestPing(uuid).orElse(-1L));
|
||||||
averages.put(uuid, PingRequestManager.calculateAverageLatency(data.pingHistory));
|
averages.put(uuid, PingRequestManager.calculateAverageLatency(data.pingHistory));
|
||||||
|
|
@ -344,7 +340,7 @@ public final class PingRequestManager {
|
||||||
PingStats globalStats = getGlobalPingStats();
|
PingStats globalStats = getGlobalPingStats();
|
||||||
NetworkHandler.sendPingReport(player, latestPings, averages, globalStats);
|
NetworkHandler.sendPingReport(player, latestPings, averages, globalStats);
|
||||||
}
|
}
|
||||||
private static void updatePingHistory(@NotNull PlayerPingData data, long ping) {
|
private static void updatePingHistory(PlayerPingData data, long ping) {
|
||||||
data.addPing(ping);
|
data.addPing(ping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +348,7 @@ public final class PingRequestManager {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
playerData.forEach((playerId, data) -> {
|
playerData.forEach((playerId, data) -> {
|
||||||
synchronized (PingRequestManager.class) {
|
synchronized (data) {
|
||||||
data.activeRequests.entrySet().removeIf(entry ->
|
data.activeRequests.entrySet().removeIf(entry ->
|
||||||
currentTime - entry.getValue() > DEFAULT_TIMEOUT_MS
|
currentTime - entry.getValue() > DEFAULT_TIMEOUT_MS
|
||||||
);
|
);
|
||||||
|
|
@ -368,22 +364,29 @@ public final class PingRequestManager {
|
||||||
|
|
||||||
|
|
||||||
private static double calculateAverageLatency(Collection<Long> pingHistory) {
|
private static double calculateAverageLatency(Collection<Long> pingHistory) {
|
||||||
return PlayerPingData.calculate(pingHistory);
|
if (pingHistory.isEmpty()) return 0;
|
||||||
|
|
||||||
|
double total = 0;
|
||||||
|
double weightSum = 0;
|
||||||
|
int i = 1;
|
||||||
|
|
||||||
|
for (Long ping : pingHistory) {
|
||||||
|
double weight = 1.0 / i;
|
||||||
|
total += ping * weight;
|
||||||
|
weightSum += weight;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total / weightSum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Contract(pure = true)
|
private static double calculatePacketLossRate(PlayerPingData data) {
|
||||||
private static double calculatePacketLossRate(@NotNull PlayerPingData data) {
|
synchronized (data) {
|
||||||
synchronized (PingRequestManager.class) {
|
|
||||||
if (data.totalRequests == 0) return 0;
|
if (data.totalRequests == 0) return 0;
|
||||||
return (1 - (double) data.successfulRequests / data.totalRequests) * 100;
|
return (1 - (double) data.successfulRequests / data.totalRequests) * 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void close() {
|
|
||||||
pingScheduler.shutdownNow();
|
|
||||||
scheduler.shutdownNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 数据结构 ==========
|
// ========== 数据结构 ==========
|
||||||
|
|
||||||
private static class PlayerPingData {
|
private static class PlayerPingData {
|
||||||
|
|
@ -403,11 +406,6 @@ public final class PingRequestManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateAverageLatency(Collection<Long> pings) {
|
private double calculateAverageLatency(Collection<Long> pings) {
|
||||||
return calculate(pings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
static double calculate(@NotNull Collection<Long> pings) {
|
|
||||||
if (pings.isEmpty()) return 0;
|
if (pings.isEmpty()) return 0;
|
||||||
|
|
||||||
double total = 0;
|
double total = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.network.toClient;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
|
||||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
|
||||||
import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type Common config hash inform packet.
|
|
||||||
*/
|
|
||||||
public record CommonConfigHashInformPacket(int hash) {
|
|
||||||
/**
|
|
||||||
* Encode.
|
|
||||||
*
|
|
||||||
* @param packet the packet
|
|
||||||
* @param buffer the buffer
|
|
||||||
*/
|
|
||||||
public static void encode(@NotNull CommonConfigHashInformPacket packet, @NotNull FriendlyByteBuf buffer) {
|
|
||||||
buffer.writeInt(packet.hash());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode common config hash inform packet.
|
|
||||||
*
|
|
||||||
* @param buffer the buffer
|
|
||||||
* @return the common config hash inform packet
|
|
||||||
*/
|
|
||||||
@Contract("_ -> new")
|
|
||||||
public static @NotNull CommonConfigHashInformPacket decode(@NotNull FriendlyByteBuf buffer) {
|
|
||||||
return new CommonConfigHashInformPacket(buffer.readInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle.
|
|
||||||
*
|
|
||||||
* @param packet the packet
|
|
||||||
* @param ctx the ctx
|
|
||||||
*/
|
|
||||||
public static void handle(CommonConfigHashInformPacket packet, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
|
||||||
NetworkEvent.Context context = ctx.get();
|
|
||||||
context.enqueueWork(() -> {
|
|
||||||
int hash = CrossServerConfigManager.INSTANCE.calculateConfigHash();
|
|
||||||
if (hash != packet.hash()) {
|
|
||||||
NetworkHandler.CHANNEL.sendToServer(new SyncCommonConfigRequestPacket(hash));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
context.setPacketHandled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.network.toClient;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public record GotoServerPayload(String serverName) {
|
|
||||||
public static void encode(@NotNull GotoServerPayload payload, @NotNull FriendlyByteBuf buf) {
|
|
||||||
buf.writeUtf(payload.serverName);
|
|
||||||
}
|
|
||||||
@Contract("_ -> new")
|
|
||||||
public static @NotNull GotoServerPayload decode(@NotNull FriendlyByteBuf buf) {
|
|
||||||
return new GotoServerPayload(buf.readUtf());
|
|
||||||
}
|
|
||||||
public static void handle(@NotNull GotoServerPayload msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
|
||||||
NetworkEvent.Context context = ctx.get();
|
|
||||||
context.enqueueWork(() -> NetworkHandler.sendTeleportRequest(msg.serverName));
|
|
||||||
context.setPacketHandled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,6 @@ import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.fml.DistExecutor;
|
import net.minecraftforge.fml.DistExecutor;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
@ -14,18 +13,18 @@ import static com.leisuretimedock.crossmod.network.NetworkHandler.CHANNEL;
|
||||||
|
|
||||||
//Server -> Client
|
//Server -> Client
|
||||||
public record PingMessagePayload(UUID requestId) {
|
public record PingMessagePayload(UUID requestId) {
|
||||||
public static void encode(@NotNull PingMessagePayload payload, @NotNull FriendlyByteBuf buf) {
|
public static void encode(PingMessagePayload payload, FriendlyByteBuf buf) {
|
||||||
buf.writeLong(payload.requestId().getMostSignificantBits());
|
buf.writeLong(payload.requestId().getMostSignificantBits());
|
||||||
buf.writeLong(payload.requestId().getLeastSignificantBits());
|
buf.writeLong(payload.requestId().getLeastSignificantBits());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull PingMessagePayload decode(@NotNull FriendlyByteBuf buf) {
|
public static PingMessagePayload decode(FriendlyByteBuf buf) {
|
||||||
long mostSignificantBits = buf.readLong();
|
long mostSignificantBits = buf.readLong();
|
||||||
long leastSignificantBits = buf.readLong();
|
long leastSignificantBits = buf.readLong();
|
||||||
return new PingMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
|
return new PingMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
|
||||||
}
|
}
|
||||||
//客户端处理
|
//客户端处理
|
||||||
public static void handle(PingMessagePayload msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
public static void handle(PingMessagePayload msg, Supplier<NetworkEvent.Context> ctx) {
|
||||||
ctx.get().enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
|
ctx.get().enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
|
||||||
// 客户端收到ping请求,立即返回pong
|
// 客户端收到ping请求,立即返回pong
|
||||||
CHANNEL.sendToServer(new PongMessagePayload(msg.requestId));
|
CHANNEL.sendToServer(new PongMessagePayload(msg.requestId));
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ package com.leisuretimedock.crossmod.network.toClient;
|
||||||
import com.leisuretimedock.crossmod.client.ClientPingHandler;
|
import com.leisuretimedock.crossmod.client.ClientPingHandler;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -21,7 +19,7 @@ public class PingResultPacket {
|
||||||
this.pingResults = new HashMap<>(pingResults);
|
this.pingResults = new HashMap<>(pingResults);
|
||||||
this.averageLatencies = new HashMap<>(averageLatencies);
|
this.averageLatencies = new HashMap<>(averageLatencies);
|
||||||
}
|
}
|
||||||
public PingResultPacket(@NotNull FriendlyByteBuf buf) {
|
public PingResultPacket(FriendlyByteBuf buf) {
|
||||||
this.timestamp = System.currentTimeMillis();
|
this.timestamp = System.currentTimeMillis();
|
||||||
int size = buf.readVarInt();
|
int size = buf.readVarInt();
|
||||||
this.pingResults = new HashMap<>(size);
|
this.pingResults = new HashMap<>(size);
|
||||||
|
|
@ -34,12 +32,11 @@ public class PingResultPacket {
|
||||||
averageLatencies.put(uuid, avgLatency);
|
averageLatencies.put(uuid, avgLatency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Contract("_ -> new")
|
public static PingResultPacket decode(FriendlyByteBuf buf) {
|
||||||
public static @NotNull PingResultPacket decode(FriendlyByteBuf buf) {
|
|
||||||
return new PingResultPacket(buf);
|
return new PingResultPacket(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void encode(@NotNull FriendlyByteBuf buf) {
|
public void encode(FriendlyByteBuf buf) {
|
||||||
buf.writeVarInt(pingResults.size());
|
buf.writeVarInt(pingResults.size());
|
||||||
pingResults.forEach((uuid, ping) -> {
|
pingResults.forEach((uuid, ping) -> {
|
||||||
buf.writeUUID(uuid);
|
buf.writeUUID(uuid);
|
||||||
|
|
@ -48,7 +45,7 @@ public class PingResultPacket {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handle(@NotNull Supplier<NetworkEvent.Context> ctx) {
|
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||||
ctx.get().enqueueWork(() -> {
|
ctx.get().enqueueWork(() -> {
|
||||||
// 检查数据时效性(5秒内有效)
|
// 检查数据时效性(5秒内有效)
|
||||||
if (System.currentTimeMillis() - timestamp < 5000) {
|
if (System.currentTimeMillis() - timestamp < 5000) {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import com.leisuretimedock.crossmod.client.ClientPingHandler;
|
||||||
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
|
@ -17,8 +15,7 @@ public class PingStatsPacket {
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Contract("_ -> new")
|
public static PingStatsPacket decode(FriendlyByteBuf buf) {
|
||||||
public static @NotNull PingStatsPacket decode(@NotNull FriendlyByteBuf buf) {
|
|
||||||
return new PingStatsPacket(
|
return new PingStatsPacket(
|
||||||
new PingRequestManager.PingStats(
|
new PingRequestManager.PingStats(
|
||||||
buf.readDouble(),
|
buf.readDouble(),
|
||||||
|
|
@ -31,7 +28,7 @@ public class PingStatsPacket {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void encode(@NotNull FriendlyByteBuf buf) {
|
public void encode(FriendlyByteBuf buf) {
|
||||||
buf.writeDouble(stats.average());
|
buf.writeDouble(stats.average());
|
||||||
buf.writeLong(stats.max());
|
buf.writeLong(stats.max());
|
||||||
buf.writeLong(stats.min());
|
buf.writeLong(stats.min());
|
||||||
|
|
@ -40,7 +37,7 @@ public class PingStatsPacket {
|
||||||
buf.writeDouble(stats.packetLossRate());
|
buf.writeDouble(stats.packetLossRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handle(@NotNull Supplier<NetworkEvent.Context> ctx) {
|
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||||
ctx.get().enqueueWork(() -> {
|
ctx.get().enqueueWork(() -> {
|
||||||
ClientPingHandler.handlePingStats(stats);
|
ClientPingHandler.handlePingStats(stats);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,11 @@ import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl;
|
import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl;
|
||||||
import net.minecraft.client.multiplayer.ServerData;
|
|
||||||
import net.minecraft.network.Connection;
|
import net.minecraft.network.Connection;
|
||||||
import net.minecraft.network.ConnectionProtocol;
|
import net.minecraft.network.ConnectionProtocol;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import net.minecraftforge.network.*;
|
import net.minecraftforge.network.*;
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
|
@ -28,8 +24,7 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
||||||
public ResetPacket() {
|
public ResetPacket() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@Contract("_ -> new")
|
public static ResetPacket decode(FriendlyByteBuf ignoredBuf) {
|
||||||
public static @NotNull ResetPacket decode(FriendlyByteBuf ignoredBuf) {
|
|
||||||
return new ResetPacket();
|
return new ResetPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,24 +32,18 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void handler(HandshakeHandler ignoredHandler, ResetPacket ignoredMsg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
public static void handler(HandshakeHandler ignoredHandler, ResetPacket ignoredMsg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||||
handler(ctxSupplier, log);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void handler(@NotNull Supplier<NetworkEvent.Context> ctxSupplier, Logger log) {
|
|
||||||
NetworkEvent.Context ctx = ctxSupplier.get();
|
NetworkEvent.Context ctx = ctxSupplier.get();
|
||||||
ClientResetManager.isNegotiating.set(true);
|
ClientResetManager.isNegotiating.set(true);
|
||||||
Connection conn = ctx.getNetworkManager();
|
Connection conn = ctx.getNetworkManager();
|
||||||
if (ctx.getDirection() != NetworkDirection.LOGIN_TO_CLIENT && ctx.getDirection() != NetworkDirection.PLAY_TO_CLIENT) {
|
if (ctx.getDirection() != NetworkDirection.LOGIN_TO_CLIENT && ctx.getDirection() != NetworkDirection.PLAY_TO_CLIENT) {
|
||||||
conn.disconnect(Component.translatable("ltd.mod.client.invalid_packet"));
|
conn.disconnect(new TranslatableComponent("ltd.mod.client.invalid_packet"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ResetHelper.clearClient(ctx)) {
|
if (ResetHelper.clearClient(ctx)) {
|
||||||
ServerData serverData = Minecraft.getInstance().getCurrentServer();
|
|
||||||
NetworkHooks.registerClientLoginChannel(conn);
|
NetworkHooks.registerClientLoginChannel(conn);
|
||||||
conn.setProtocol(ConnectionProtocol.LOGIN);
|
conn.setProtocol(ConnectionProtocol.LOGIN);
|
||||||
conn.setListener(new ClientHandshakePacketListenerImpl(
|
conn.setListener(new ClientHandshakePacketListenerImpl(
|
||||||
conn, Minecraft.getInstance(), serverData ,null, true, null, s -> {}
|
conn, Minecraft.getInstance(), null, s -> {}
|
||||||
));
|
));
|
||||||
|
|
||||||
((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn);
|
((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn);
|
||||||
|
|
@ -66,10 +55,11 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
||||||
);
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to send acknowledgment", e);
|
log.error("Failed to send acknowledgment", e);
|
||||||
conn.disconnect(Component.translatable("ltd.mod.client.error.handshake"));
|
conn.disconnect(new TranslatableComponent("ltd.mod.client.error.handshake"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.setPacketHandled(true);
|
ctx.setPacketHandled(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.network.toClient;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type Sync common config packet.
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public record SyncCommonConfigPacket(CompoundTag config, int hash) {
|
|
||||||
/**
|
|
||||||
* Encode.
|
|
||||||
*
|
|
||||||
* @param msg the msg
|
|
||||||
* @param buf the buf
|
|
||||||
*/
|
|
||||||
public static void encode(@NotNull SyncCommonConfigPacket msg, @NotNull FriendlyByteBuf buf) {
|
|
||||||
buf.writeNbt(msg.config);
|
|
||||||
buf.writeInt(msg.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode packet eternal potato remove packet.
|
|
||||||
*
|
|
||||||
* @param buf the buf
|
|
||||||
* @return the packet eternal potato remove packet
|
|
||||||
*/
|
|
||||||
@Contract("_ -> new")
|
|
||||||
public static @NotNull SyncCommonConfigPacket decode(@NotNull FriendlyByteBuf buf) {
|
|
||||||
return new SyncCommonConfigPacket(buf.readNbt(), buf.readInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle.
|
|
||||||
*
|
|
||||||
* @param msg the msg
|
|
||||||
* @param ctx the ctx
|
|
||||||
*/
|
|
||||||
public static void handle(SyncCommonConfigPacket msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
|
||||||
ctx.get().enqueueWork(() -> {
|
|
||||||
CrossServerConfigManager manager = CrossServerConfigManager.INSTANCE;
|
|
||||||
|
|
||||||
// 1. 保存当前配置(强制重新序列化,不使用缓存)
|
|
||||||
CompoundTag currentConfig = manager.serializeToNBT();
|
|
||||||
int currentHash = manager.calculateConfigHash();
|
|
||||||
|
|
||||||
// 2. 应用新配置
|
|
||||||
manager.deserializeFromNBT(msg.config);
|
|
||||||
|
|
||||||
// 3. 验证哈希
|
|
||||||
int newHash = manager.calculateConfigHash();
|
|
||||||
if (newHash != msg.hash) {
|
|
||||||
log.error("Hash mismatch! Expected: {}, Actual: {}", msg.hash, newHash);
|
|
||||||
log.error("Current hash before deserialization: {}", currentHash);
|
|
||||||
|
|
||||||
// 打印差异详情
|
|
||||||
if (currentConfig != null) {
|
|
||||||
compareConfigs(currentConfig, msg.config);
|
|
||||||
manager.deserializeFromNBT(currentConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证恢复是否成功
|
|
||||||
int restoredHash = manager.calculateConfigHash();
|
|
||||||
if (restoredHash != currentHash) {
|
|
||||||
log.error("Failed to restore config! Hash mismatch after rollback!");
|
|
||||||
} else {
|
|
||||||
log.info("Successfully rolled back to previous config");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug("Config sync successful, hash: {}", msg.hash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ctx.get().setPacketHandled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void compareConfigs(@NotNull CompoundTag oldConfig, @NotNull CompoundTag newConfig) {
|
|
||||||
Set<String> oldKeys = oldConfig.getAllKeys();
|
|
||||||
Set<String> newKeys = newConfig.getAllKeys();
|
|
||||||
|
|
||||||
// 找出只存在于旧配置的键
|
|
||||||
for (String key : oldKeys) {
|
|
||||||
if (!newConfig.contains(key)) {
|
|
||||||
log.warn("Key only in old config: {}", key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找出只存在于新配置的键
|
|
||||||
for (String key : newKeys) {
|
|
||||||
if (!oldConfig.contains(key)) {
|
|
||||||
log.warn("Key only in new config: {}", key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 比较共同键的值
|
|
||||||
for (String key : oldKeys) {
|
|
||||||
if (newConfig.contains(key) && !Objects.equals(oldConfig.get(key), newConfig.get(key))) {
|
|
||||||
log.warn("Value mismatch for key: {}", key);
|
|
||||||
log.warn(" Old: {}", oldConfig.get(key));
|
|
||||||
log.warn(" New: {}", newConfig.get(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,25 +3,24 @@ package com.leisuretimedock.crossmod.network.toServer;
|
||||||
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
//Server
|
//Server
|
||||||
public record PongMessagePayload(UUID requestId) {
|
public record PongMessagePayload(UUID requestId) {
|
||||||
public static void encode(@NotNull PongMessagePayload payload, @NotNull FriendlyByteBuf buf) {
|
public static void encode(PongMessagePayload payload, FriendlyByteBuf buf) {
|
||||||
buf.writeLong(payload.requestId().getMostSignificantBits());
|
buf.writeLong(payload.requestId().getMostSignificantBits());
|
||||||
buf.writeLong(payload.requestId().getLeastSignificantBits());
|
buf.writeLong(payload.requestId().getLeastSignificantBits());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull PongMessagePayload decode(@NotNull FriendlyByteBuf buf) {
|
public static PongMessagePayload decode(FriendlyByteBuf buf) {
|
||||||
long mostSignificantBits = buf.readLong();
|
long mostSignificantBits = buf.readLong();
|
||||||
long leastSignificantBits = buf.readLong();
|
long leastSignificantBits = buf.readLong();
|
||||||
return new PongMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
|
return new PongMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
|
||||||
}
|
}
|
||||||
//服务器处理
|
//服务器处理
|
||||||
public static void handle(PongMessagePayload msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
public static void handle(PongMessagePayload msg, Supplier<NetworkEvent.Context> ctx) {
|
||||||
ctx.get().enqueueWork(() -> {
|
ctx.get().enqueueWork(() -> {
|
||||||
PingRequestManager.complete(ctx.get().getSender(),msg.requestId);
|
PingRequestManager.complete(ctx.get().getSender(),msg.requestId);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.network.toServer;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
|
||||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
|
||||||
import com.leisuretimedock.crossmod.network.toClient.SyncCommonConfigPacket;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type Sync common config request packet.
|
|
||||||
*/
|
|
||||||
public record SyncCommonConfigRequestPacket(int hash) {
|
|
||||||
/**
|
|
||||||
* Encode.
|
|
||||||
*
|
|
||||||
* @param msg the msg
|
|
||||||
* @param buf the buf
|
|
||||||
*/
|
|
||||||
public static void encode(@NotNull SyncCommonConfigRequestPacket msg, @NotNull FriendlyByteBuf buf) {
|
|
||||||
buf.writeInt(msg.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode sync common config request packet.
|
|
||||||
*
|
|
||||||
* @param buf the buf
|
|
||||||
* @return the sync common config request packet
|
|
||||||
*/
|
|
||||||
@Contract("_ -> new")
|
|
||||||
public static @NotNull SyncCommonConfigRequestPacket decode(@NotNull FriendlyByteBuf buf) {
|
|
||||||
return new SyncCommonConfigRequestPacket(buf.readInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle.
|
|
||||||
*
|
|
||||||
* @param msg the msg
|
|
||||||
* @param ctx the ctx
|
|
||||||
*/
|
|
||||||
public static void handle(SyncCommonConfigRequestPacket msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
|
||||||
ctx.get().enqueueWork(() -> {
|
|
||||||
if (msg.hash != CrossServerConfigManager.INSTANCE.cacheHash) {
|
|
||||||
NetworkHandler.sendToPlayer(new SyncCommonConfigPacket(CrossServerConfigManager.INSTANCE.serializeToNBT(), CrossServerConfigManager.INSTANCE.calculateConfigHash()), ctx.get().getSender());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ctx.get().setPacketHandled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,8 +12,6 @@ import net.minecraftforge.network.NetworkConstants;
|
||||||
import net.minecraftforge.network.NetworkDirection;
|
import net.minecraftforge.network.NetworkDirection;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
import net.minecraftforge.network.simple.SimpleChannel;
|
import net.minecraftforge.network.simple.SimpleChannel;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
@ -27,7 +25,7 @@ public class ClientResetManager {
|
||||||
public static AtomicBoolean isNegotiating = new AtomicBoolean(false);
|
public static AtomicBoolean isNegotiating = new AtomicBoolean(false);
|
||||||
public static SimpleChannel handshakeChannel;
|
public static SimpleChannel handshakeChannel;
|
||||||
|
|
||||||
public static void init(@NotNull FMLCommonSetupEvent event) {
|
public static void init(FMLCommonSetupEvent event) {
|
||||||
|
|
||||||
event.enqueueWork(() -> {
|
event.enqueueWork(() -> {
|
||||||
if (handshakeField == null) {
|
if (handshakeField == null) {
|
||||||
|
|
@ -47,7 +45,7 @@ public class ClientResetManager {
|
||||||
.loginIndex(ResetPacket::getLoginIndex, ResetPacket::setLoginIndex)
|
.loginIndex(ResetPacket::getLoginIndex, ResetPacket::setLoginIndex)
|
||||||
.decoder(ResetPacket::decode)
|
.decoder(ResetPacket::decode)
|
||||||
.encoder(ResetPacket::encode)
|
.encoder(ResetPacket::encode)
|
||||||
.consumerMainThread(HandshakeHandler.biConsumerFor(ResetPacket::handler))
|
.consumer(HandshakeHandler.biConsumerFor(ResetPacket::handler))
|
||||||
.add();
|
.add();
|
||||||
log.info( "Registered forge reset packet successfully.");
|
log.info( "Registered forge reset packet successfully.");
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +55,7 @@ public class ClientResetManager {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private static @Nullable Field fetchHandshakeChannel() {
|
private static Field fetchHandshakeChannel() {
|
||||||
try {
|
try {
|
||||||
return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel");
|
return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel");
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +65,7 @@ public class ClientResetManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable Constructor<NetworkEvent.Context> fetchNetworkEventContext() {
|
private static Constructor<NetworkEvent.Context> fetchNetworkEventContext() {
|
||||||
try {
|
try {
|
||||||
return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class);
|
return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,13 @@ package com.leisuretimedock.crossmod.reset;
|
||||||
import com.leisuretimedock.crossmod.client.gui.GenericIceMessageScreen;
|
import com.leisuretimedock.crossmod.client.gui.GenericIceMessageScreen;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.client.multiplayer.ServerData;
|
||||||
import net.minecraft.server.packs.repository.Pack;
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
import net.minecraftforge.network.NetworkEvent;
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
import net.minecraftforge.registries.GameData;
|
import net.minecraftforge.registries.GameData;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
|
@ -20,26 +18,17 @@ import static net.minecraft.ChatFormatting.BOLD;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@OnlyIn(Dist.CLIENT)
|
@OnlyIn(Dist.CLIENT)
|
||||||
public class ResetHelper {
|
public class ResetHelper {
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
public static boolean clearClient(NetworkEvent.Context context) {
|
||||||
public static boolean clearClient(NetworkEvent.@NotNull Context context) {
|
|
||||||
CompletableFuture<Void> future = context.enqueueWork(() -> {
|
CompletableFuture<Void> future = context.enqueueWork(() -> {
|
||||||
log.debug("Clearing");
|
log.debug("Clearing");
|
||||||
Minecraft minecraft = Minecraft.getInstance();
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
Pack serverPack = Minecraft.getInstance().getDownloadedPackSource().serverPack;
|
ServerData serverData = minecraft.getCurrentServer();
|
||||||
if (minecraft.level == null) {
|
if (minecraft.level == null) {
|
||||||
GameData.revertToFrozen();
|
GameData.revertToFrozen();
|
||||||
}
|
}
|
||||||
Minecraft.getInstance().getDownloadedPackSource().serverPack = null;
|
|
||||||
minecraft.clearLevel(new GenericIceMessageScreen(Component.translatable("ltd.mod.client.negotiating").withStyle(BOLD)));
|
minecraft.clearLevel(new GenericIceMessageScreen(new TranslatableComponent("ltd.mod.client.negotiating").withStyle(BOLD)));
|
||||||
try {
|
minecraft.setCurrentServer(serverData);
|
||||||
context.getNetworkManager().channel().pipeline().remove("forge:forge_fixes");
|
|
||||||
} catch (NoSuchElementException ignored) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
context.getNetworkManager().channel().pipeline().remove("forge:vanilla_filter");
|
|
||||||
} catch (NoSuchElementException ignored) {
|
|
||||||
}
|
|
||||||
Minecraft.getInstance().getDownloadedPackSource().serverPack = serverPack;
|
|
||||||
});
|
});
|
||||||
log.debug("Waiting for Clear to complete");
|
log.debug("Waiting for Clear to complete");
|
||||||
try {
|
try {
|
||||||
|
|
@ -48,7 +37,7 @@ public class ResetHelper {
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to clear client connection", e);
|
log.error("Failed to clear client connection", e);
|
||||||
Objects.requireNonNull(Minecraft.getInstance().getConnection()).onDisconnect(Component.translatable("ltd.mod.client.failed.reset_connection"));
|
Objects.requireNonNull(Minecraft.getInstance().getConnection()).onDisconnect(new TranslatableComponent("ltd.mod.client.failed.reset_connection"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
package com.leisuretimedock.crossmod.reset;
|
package com.leisuretimedock.crossmod.reset;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossmod.mixin.AccessorMinecraft;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl;
|
||||||
|
import net.minecraft.network.Connection;
|
||||||
|
import net.minecraft.network.ConnectionProtocol;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
import net.minecraftforge.network.*;
|
import net.minecraftforge.network.*;
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
|
@ -18,8 +22,7 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
||||||
public ResetPacket() {
|
public ResetPacket() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@Contract("_ -> new")
|
public static ResetPacket decode(FriendlyByteBuf buf) {
|
||||||
public static @NotNull ResetPacket decode(FriendlyByteBuf buf) {
|
|
||||||
return new ResetPacket();
|
return new ResetPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,7 +30,33 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void handler(HandshakeHandler handler , ResetPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
public static void handler(HandshakeHandler handler , ResetPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||||
com.leisuretimedock.crossmod.network.toClient.ResetPacket.handler(ctxSupplier, log);
|
NetworkEvent.Context ctx = ctxSupplier.get();
|
||||||
|
ClientResetManager.isNegotiating.set(true);
|
||||||
|
Connection conn = ctx.getNetworkManager();
|
||||||
|
if (ctx.getDirection() != NetworkDirection.LOGIN_TO_CLIENT && ctx.getDirection() != NetworkDirection.PLAY_TO_CLIENT) {
|
||||||
|
conn.disconnect(new TranslatableComponent("ltd.mod.client.invalid_packet"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ResetHelper.clearClient(ctx)) {
|
||||||
|
NetworkHooks.registerClientLoginChannel(conn);
|
||||||
|
conn.setProtocol(ConnectionProtocol.LOGIN);
|
||||||
|
conn.setListener(new ClientHandshakePacketListenerImpl(
|
||||||
|
conn, Minecraft.getInstance(), null, s -> {}
|
||||||
|
));
|
||||||
|
|
||||||
|
((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientResetManager.handshakeChannel.reply(
|
||||||
|
new HandshakeMessages.C2SAcknowledge(),
|
||||||
|
ClientResetManager.contextConstructor.newInstance(conn, NetworkDirection.LOGIN_TO_CLIENT, 98)
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to send acknowledgment", e);
|
||||||
|
conn.disconnect(new TranslatableComponent("ltd.mod.client.error.handshake"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.setPacketHandled(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,174 +1,57 @@
|
||||||
package com.leisuretimedock.crossmod.util;
|
package com.leisuretimedock.crossmod.util;
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
|
|
||||||
public class DebugUtils {
|
public class DebugUtils {
|
||||||
|
public static void debugBuffer(FriendlyByteBuf buf) {
|
||||||
private static PrintWriter logWriter = null;
|
|
||||||
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
||||||
|
|
||||||
private static PrintWriter getLogWriter() {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isLogToFile()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (logWriter == null) {
|
|
||||||
try {
|
|
||||||
String logPath = CrossCommonModConfig.getInstance().getLogFilePath();
|
|
||||||
logWriter = new PrintWriter(new FileWriter(logPath, true), true);
|
|
||||||
logWriter.println("=== CrossMod Debug Log Started at " + LocalDateTime.now().format(TIME_FORMAT) + " ===");
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("[CrossMod] Failed to create log file: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return logWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void log(String message) {
|
|
||||||
// 控制台输出
|
|
||||||
System.out.println(message);
|
|
||||||
|
|
||||||
// 文件输出
|
|
||||||
PrintWriter writer = getLogWriter();
|
|
||||||
if (writer != null) {
|
|
||||||
writer.println(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isDebugEnabled() {
|
|
||||||
return CrossCommonModConfig.getInstance().isDebugPackets();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void debugBuffer(@NotNull FriendlyByteBuf buf) {
|
|
||||||
debugBuffer(buf, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void debugBuffer(@NotNull FriendlyByteBuf buf, String caller) {
|
|
||||||
if (!isDebugEnabled()) return;
|
|
||||||
|
|
||||||
int readable = buf.readableBytes();
|
int readable = buf.readableBytes();
|
||||||
int readerIdx = buf.readerIndex();
|
System.out.println("[Debug] Readable bytes: " + readable);
|
||||||
int writerIdx = buf.writerIndex();
|
|
||||||
|
|
||||||
log("[Debug][" + caller + "] ====================");
|
|
||||||
log("[Debug][" + caller + "] readerIndex: " + readerIdx);
|
|
||||||
log("[Debug][" + caller + "] writerIndex: " + writerIdx);
|
|
||||||
log("[Debug][" + caller + "] readableBytes: " + readable);
|
|
||||||
|
|
||||||
if (readable <= 0) {
|
if (readable <= 0) {
|
||||||
log("[Debug][" + caller + "] No bytes left to inspect.");
|
System.out.println("[Debug] No extra bytes to inspect.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes();
|
|
||||||
int bytesToRead = Math.min(readable, maxBytes);
|
|
||||||
|
|
||||||
// 保存当前位置
|
// 保存当前位置
|
||||||
int index = buf.readerIndex();
|
int index = buf.readerIndex();
|
||||||
|
|
||||||
// 读取并打印十六进制
|
// 读取并打印十六进制
|
||||||
byte[] bytes = new byte[bytesToRead];
|
byte[] bytes = new byte[readable];
|
||||||
buf.readBytes(bytes);
|
buf.readBytes(bytes);
|
||||||
String hex = HexFormat.of().formatHex(bytes);
|
String hex = HexFormat.of().formatHex(bytes);
|
||||||
log("[Debug][" + caller + "] Bytes (hex): " + hex);
|
System.out.println("[Debug] Extra bytes (hex): " + hex);
|
||||||
|
|
||||||
if (bytesToRead < readable) {
|
// 尝试以 UTF-8 解码(仅用于辅助分析)
|
||||||
log("[Debug][" + caller + "] ... (truncated, " + (readable - bytesToRead) + " more bytes)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试以 UTF-8 解码
|
|
||||||
try {
|
try {
|
||||||
String utf8 = new String(bytes, StandardCharsets.UTF_8);
|
String utf8 = new String(bytes, StandardCharsets.UTF_8);
|
||||||
String printable = utf8.chars()
|
System.out.println("[Debug] Interpreted as UTF-8 string:\n" + utf8);
|
||||||
.mapToObj(c -> (char) c)
|
|
||||||
.filter(c -> c >= 32 && c < 127 || c == '\n' || c == '\r')
|
|
||||||
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
|
|
||||||
.toString();
|
|
||||||
if (!printable.isEmpty()) {
|
|
||||||
log("[Debug][" + caller + "] As UTF-8 (printable): " + printable);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// ignore
|
System.out.println("[Debug] Failed to interpret as UTF-8: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 还原读取位置
|
// 还原读取位置,避免影响其他逻辑
|
||||||
buf.readerIndex(index);
|
buf.readerIndex(index);
|
||||||
}
|
}
|
||||||
|
public static void debugFullBuffer(FriendlyByteBuf buf) {
|
||||||
public static void debugFullBuffer(@NotNull FriendlyByteBuf buf) {
|
ByteBuf internal = buf.copy(); // 复制整个缓冲区(包括所有字节)
|
||||||
debugFullBuffer(buf, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void debugFullBuffer(@NotNull FriendlyByteBuf buf, String caller) {
|
|
||||||
if (!isDebugEnabled()) return;
|
|
||||||
|
|
||||||
ByteBuf internal = buf.copy();
|
|
||||||
int size = internal.readableBytes();
|
int size = internal.readableBytes();
|
||||||
byte[] data = new byte[size];
|
byte[] data = new byte[size];
|
||||||
internal.readBytes(data);
|
internal.readBytes(data);
|
||||||
|
|
||||||
log("[Debug][" + caller + "] Full buffer size: " + size);
|
System.out.println("[Debug] Full buffer size: " + size);
|
||||||
|
System.out.println("[Debug] Hex dump:\n" + HexFormat.of().formatHex(data));
|
||||||
|
|
||||||
int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes();
|
try {
|
||||||
int hexSize = Math.min(size, maxBytes);
|
String utf8 = new String(data, StandardCharsets.UTF_8);
|
||||||
byte[] hexData = new byte[hexSize];
|
System.out.println("[Debug] UTF-8 decoded:\n" + utf8);
|
||||||
System.arraycopy(data, 0, hexData, 0, hexSize);
|
} catch (Exception e) {
|
||||||
log("[Debug][" + caller + "] Hex dump (first " + hexSize + " bytes):\n" + HexFormat.of().formatHex(hexData));
|
System.out.println("[Debug] UTF-8 decode failed: " + e.getMessage());
|
||||||
|
|
||||||
if (size > hexSize) {
|
|
||||||
log("[Debug][" + caller + "] ... (truncated, " + (size - hexSize) + " more bytes)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal.release();
|
internal.release(); // 手动释放 copy() 出来的 ByteBuf,防止泄漏
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 专门用于检测 BundleDelimiterPacket 的额外字节问题
|
|
||||||
public static void checkBundleDelimiterPacket(@NotNull FriendlyByteBuf buf) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) return;
|
|
||||||
|
|
||||||
int remaining = buf.readableBytes();
|
|
||||||
log("[Debug][BundleCheck] Remaining bytes after packet: " + remaining);
|
|
||||||
|
|
||||||
if (remaining > 0) {
|
|
||||||
log("[Debug][BundleCheck] *** EXTRA BYTES FOUND: " + remaining + " bytes ***");
|
|
||||||
|
|
||||||
int savedIdx = buf.readerIndex();
|
|
||||||
|
|
||||||
byte[] extra = new byte[remaining];
|
|
||||||
buf.readBytes(extra);
|
|
||||||
|
|
||||||
if (remaining >= 1) {
|
|
||||||
int possiblePacketId = extra[0] & 0xFF;
|
|
||||||
log("[Debug][BundleCheck] First extra byte (possible next packet ID): 0x" + Integer.toHexString(possiblePacketId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印前几个字节的详细信息
|
|
||||||
int showBytes = Math.min(remaining, 32);
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (int i = 0; i < showBytes; i++) {
|
|
||||||
sb.append(String.format("%02X ", extra[i] & 0xFF));
|
|
||||||
}
|
|
||||||
log("[Debug][BundleCheck] Extra bytes hex: " + sb.toString());
|
|
||||||
|
|
||||||
buf.readerIndex(savedIdx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭日志文件(在模组卸载时调用)
|
|
||||||
public static void closeLog() {
|
|
||||||
if (logWriter != null) {
|
|
||||||
logWriter.println("=== CrossMod Debug Log Ended at " + LocalDateTime.now().format(TIME_FORMAT) + " ===");
|
|
||||||
logWriter.close();
|
|
||||||
logWriter = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,324 +0,0 @@
|
||||||
package com.leisuretimedock.crossmod.util;
|
|
||||||
|
|
||||||
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跨模组调试日志工具
|
|
||||||
* 支持控制台输出和文件输出,可通过配置文件控制开关
|
|
||||||
*/
|
|
||||||
public class ModLogger {
|
|
||||||
|
|
||||||
private static final String PREFIX = "[CrossMod]";
|
|
||||||
private static final String BUNDLE_PREFIX = "[CrossMod][Bundle]";
|
|
||||||
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger("CrossMod");
|
|
||||||
private static PrintWriter fileWriter = null;
|
|
||||||
private static boolean fileLoggingEnabled = false;
|
|
||||||
private static boolean initAttempted = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化文件日志输出
|
|
||||||
*/
|
|
||||||
public static void initFileLogging() {
|
|
||||||
if (initAttempted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initAttempted = true;
|
|
||||||
|
|
||||||
CrossCommonModConfig config = CrossCommonModConfig.getInstance();
|
|
||||||
|
|
||||||
// 输出配置状态
|
|
||||||
LOGGER.info(PREFIX + " Config - debugPackets: {}, debugBundleDelimiter: {}, logToFile: {}",
|
|
||||||
config.isDebugPackets(), config.isDebugBundleDelimiter(), config.isLogToFile());
|
|
||||||
|
|
||||||
if (!config.isLogToFile()) {
|
|
||||||
LOGGER.info(PREFIX + " File logging is disabled in config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String logPath = config.getLogFilePath();
|
|
||||||
if (logPath == null || logPath.isEmpty()) {
|
|
||||||
logPath = "logs/crossmod-debug.log";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
File logFile = new File(logPath);
|
|
||||||
File parentDir = logFile.getParentFile();
|
|
||||||
if (parentDir != null && !parentDir.exists()) {
|
|
||||||
boolean created = parentDir.mkdirs();
|
|
||||||
LOGGER.info(PREFIX + " Created log directory: {} - {}", parentDir.getAbsolutePath(), created);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试文件是否可写
|
|
||||||
boolean canWrite = logFile.exists() ? logFile.canWrite() : true;
|
|
||||||
LOGGER.info(PREFIX + " Log file path: {}, can write: {}", logFile.getAbsolutePath(), canWrite);
|
|
||||||
|
|
||||||
// 追加模式打开文件
|
|
||||||
fileWriter = new PrintWriter(new FileWriter(logFile, true), true);
|
|
||||||
fileLoggingEnabled = true;
|
|
||||||
|
|
||||||
writeToFileOnly("========================================");
|
|
||||||
writeToFileOnly("CrossMod Debug Log Started");
|
|
||||||
writeToFileOnly("Time: " + LocalDateTime.now().format(TIME_FORMAT));
|
|
||||||
writeToFileOnly("========================================");
|
|
||||||
writeToFileOnly("Config: debugPackets=" + config.isDebugPackets() +
|
|
||||||
", debugBundleDelimiter=" + config.isDebugBundleDelimiter());
|
|
||||||
writeToFileOnly("");
|
|
||||||
|
|
||||||
LOGGER.info(PREFIX + " File logging enabled: " + logFile.getAbsolutePath());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(PREFIX + " Failed to enable file logging: " + e.getMessage(), e);
|
|
||||||
fileLoggingEnabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭文件日志
|
|
||||||
*/
|
|
||||||
public static void closeFileLogging() {
|
|
||||||
if (fileWriter != null) {
|
|
||||||
writeToFileOnly("========================================");
|
|
||||||
writeToFileOnly("CrossMod Debug Log Ended");
|
|
||||||
writeToFileOnly("Time: " + LocalDateTime.now().format(TIME_FORMAT));
|
|
||||||
writeToFileOnly("========================================");
|
|
||||||
|
|
||||||
fileWriter.close();
|
|
||||||
fileWriter = null;
|
|
||||||
}
|
|
||||||
fileLoggingEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 写入文件(不输出到控制台)
|
|
||||||
*/
|
|
||||||
private static void writeToFileOnly(String message) {
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化消息(将 {} 替换为参数)
|
|
||||||
* Log4j 风格:使用 {} 作为占位符
|
|
||||||
*/
|
|
||||||
private static String formatMessage(String format, Object... args) {
|
|
||||||
if (args == null || args.length == 0) {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
String result = format;
|
|
||||||
for (Object arg : args) {
|
|
||||||
String argStr = arg != null ? arg.toString() : "null";
|
|
||||||
result = result.replaceFirst("\\{\\}", argStr);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用信息日志
|
|
||||||
*/
|
|
||||||
public static void info(String message) {
|
|
||||||
LOGGER.info(PREFIX + " " + message);
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(PREFIX + " " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void info(String format, Object... args) {
|
|
||||||
String message = formatMessage(format, args);
|
|
||||||
info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 警告日志
|
|
||||||
*/
|
|
||||||
public static void warn(String message) {
|
|
||||||
LOGGER.warn(PREFIX + " " + message);
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(PREFIX + " [WARN] " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void warn(String format, Object... args) {
|
|
||||||
String message = formatMessage(format, args);
|
|
||||||
warn(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误日志(总是输出)
|
|
||||||
*/
|
|
||||||
public static void error(String message) {
|
|
||||||
LOGGER.error(PREFIX + " [ERROR] " + message);
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(PREFIX + " [ERROR] " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void error(String format, Object... args) {
|
|
||||||
String message = formatMessage(format, args);
|
|
||||||
error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试日志(受 debugPackets 配置控制)
|
|
||||||
*/
|
|
||||||
public static void debug(String message) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugPackets()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOGGER.debug(PREFIX + " [DEBUG] " + message);
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(PREFIX + " [DEBUG] " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void debug(String format, Object... args) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugPackets()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String message = formatMessage(format, args);
|
|
||||||
debug(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bundle 专用调试日志(受 debugBundleDelimiter 配置控制)
|
|
||||||
*/
|
|
||||||
public static void bundle(String message) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String formatted = BUNDLE_PREFIX + " " + message;
|
|
||||||
LOGGER.debug(formatted);
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(formatted);
|
|
||||||
fileWriter.flush(); // 确保立即写入
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void bundle(String format, Object... args) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String message = formatMessage(format, args);
|
|
||||||
bundle(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印十六进制数据(Bundle 调试用)
|
|
||||||
*/
|
|
||||||
public static void bundleHex(String title, byte[] data, int offset, int length) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = Math.min(length, data.length - offset);
|
|
||||||
if (len <= 0) {
|
|
||||||
bundle("{}: (empty)", title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder hex = new StringBuilder();
|
|
||||||
StringBuilder ascii = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
byte b = data[offset + i];
|
|
||||||
hex.append(String.format("%02X ", b & 0xFF));
|
|
||||||
char c = (char) (b & 0xFF);
|
|
||||||
ascii.append((c >= 32 && c < 127) ? c : '.');
|
|
||||||
|
|
||||||
// 每16字节换行
|
|
||||||
if ((i + 1) % 16 == 0 && i + 1 < len) {
|
|
||||||
bundle("{}: {}", title, hex.toString());
|
|
||||||
bundle("{} {}", title, ascii.toString());
|
|
||||||
hex.setLength(0);
|
|
||||||
ascii.setLength(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hex.length() > 0) {
|
|
||||||
bundle("{}: {}", title, hex.toString());
|
|
||||||
bundle("{} {}", title, ascii.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印堆栈跟踪(Bundle 调试用)
|
|
||||||
*/
|
|
||||||
public static void bundleStackTrace() {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bundle("Stack trace:");
|
|
||||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
|
||||||
for (int i = 2; i < Math.min(stack.length, 12); i++) {
|
|
||||||
bundle(" at {}", stack[i].toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印分隔线(Bundle 调试用)
|
|
||||||
*/
|
|
||||||
public static void bundleSeparator() {
|
|
||||||
bundle("========================================");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印带标题的分隔线
|
|
||||||
*/
|
|
||||||
public static void bundleSection(String title) {
|
|
||||||
bundle("");
|
|
||||||
bundle("========== " + title + " ==========");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void bundleVerbose(String message) {
|
|
||||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (CrossCommonModConfig.getInstance().isDebugPackets()) {
|
|
||||||
String formatted = BUNDLE_PREFIX + " [VERBOSE] " + message;
|
|
||||||
LOGGER.debug(formatted);
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(formatted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void bundleVerbose(String format, Object... args) {
|
|
||||||
String message = formatMessage(format, args);
|
|
||||||
bundleVerbose(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制写入日志(忽略配置开关,用于关键错误)
|
|
||||||
*/
|
|
||||||
public static void force(String message) {
|
|
||||||
LOGGER.error(PREFIX + " [FORCE] " + message);
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.println(PREFIX + " [FORCE] " + message);
|
|
||||||
fileWriter.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void force(String format, Object... args) {
|
|
||||||
String message = formatMessage(format, args);
|
|
||||||
force(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查文件日志是否可用(用于调试)
|
|
||||||
*/
|
|
||||||
public static boolean isFileLoggingEnabled() {
|
|
||||||
return fileLoggingEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
public net.minecraft.client.resources.DownloadedPackSource f_244082_ # serverPack
|
public net.minecraft.client.Minecraft pendingConnection #pendingConnection
|
||||||
public net.minecraft.client.Minecraft f_91009_ # pendingConnection
|
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,14 @@
|
||||||
"ltd.mod.client.name.trans_server": "LTD Cross Server Mod",
|
"ltd.mod.client.name.trans_server": "LTD Cross Server Mod",
|
||||||
"ltd.mod.client.key": "Open LTD Cross Server Menu",
|
"ltd.mod.client.key": "Open LTD Cross Server Menu",
|
||||||
"ltd.mod.client.menu": "Cross Server Menu",
|
"ltd.mod.client.menu": "Cross Server Menu",
|
||||||
"ltd.mod.client.menu.button.hub": "H Hub",
|
"ltd.mod.client.menu.button.1": "H Hub",
|
||||||
"ltd.mod.client.menu.button.survival": "S Survival",
|
"ltd.mod.client.menu.button.2": "S Survival",
|
||||||
"ltd.mod.client.menu.button.skyblock": "S SkyBlock",
|
|
||||||
"ltd.mod.client.menu.button.resource": "R Resource",
|
|
||||||
"ltd.mod.client.menu.button.minigame": "M MiniGame",
|
|
||||||
"ltd.mod.client.menu.button.custom": "{icon} ${name}",
|
|
||||||
"ltd.mod.client.menu.search": "Search servers...",
|
|
||||||
"ltd.mod.client.menu.checkbox.show_trans_tip": "Show cross server tip overlay",
|
"ltd.mod.client.menu.checkbox.show_trans_tip": "Show cross server tip overlay",
|
||||||
"ltd.mod.client.menu.checkbox.show_ping_stat": "Show ping stat overlay",
|
"ltd.mod.client.menu.checkbox.show_ping_stat": "Show ping stat overlay",
|
||||||
"ltd.mod.client.negotiating": "Negotiating...",
|
"ltd.mod.client.negotiating": "Negotiating...",
|
||||||
"ltd.mod.client.failed.reset_connection": "Failed to reset connection.",
|
"ltd.mod.client.failed.reset_connection": "Failed to reset connection.",
|
||||||
"ltd.mod.client.error.handshake": "Handshake error",
|
"ltd.mod.client.error.handshake": "Handshake error",
|
||||||
"ltd.mod.client.invalid_reset_packet": "Invalid reset packet",
|
"ltd.mod.client.invalid_reset_packet": "Invalid reset packet",
|
||||||
"ltd.mod.client.menu.button.no_servers": "No servers",
|
|
||||||
"ltd.mod.client.overlay.tip": "use [%s] to open cross server menu",
|
|
||||||
"ltd.mod.client.invalid_packet": "Invalid packet",
|
"ltd.mod.client.invalid_packet": "Invalid packet",
|
||||||
"ltd.mod.client.request.goto": "Request goto %s",
|
"ltd.mod.client.request.goto": "Request goto %s",
|
||||||
"ltd.mod.ping.error.no_players": "No valid players specified",
|
"ltd.mod.ping.error.no_players": "No valid players specified",
|
||||||
|
|
@ -43,21 +36,6 @@
|
||||||
"ltd.mod.ping.stats.avg_latency": "Average delay: %.1fms",
|
"ltd.mod.ping.stats.avg_latency": "Average delay: %.1fms",
|
||||||
"ltd.mod.ping.stats.packet_loss": "Packet loss rate: %.1f%%",
|
"ltd.mod.ping.stats.packet_loss": "Packet loss rate: %.1f%%",
|
||||||
"ltd.mod.ping.stats.sample_count": "Sample count: %d",
|
"ltd.mod.ping.stats.sample_count": "Sample count: %d",
|
||||||
"ltd.mod.ping.warn.network_latency": "Network latency is high, so it is recommended to reduce the ping frequency",
|
"ltd.mod.ping.warn.network_latency": "Network latency is high, so it is recommended to reduce the ping frequency"
|
||||||
"ltd.mod.client.menu.command.header": "[=== LTD Cross Server Menu ===]",
|
|
||||||
"ltd.mod.client.menu.command.hover": "Click to teleport to %s",
|
|
||||||
"crossmod.command.debug.packets.enabled": "[CrossMod] Packet debugging enabled",
|
|
||||||
"crossmod.command.debug.packets.disabled": "[CrossMod] Packet debugging disabled",
|
|
||||||
"crossmod.command.debug.bundledebug.enabled": "[CrossMod] BundleDelimiter debugging enabled",
|
|
||||||
"crossmod.command.debug.bundledebug.disabled": "[CrossMod] BundleDelimiter debugging disabled",
|
|
||||||
"crossmod.command.debug.status.title": "[CrossMod] Debug status:",
|
|
||||||
"crossmod.command.debug.status.packet": "Packet Debug: ",
|
|
||||||
"crossmod.command.debug.status.bundledebug": "BundleDelimiter Debug: ",
|
|
||||||
"crossmod.command.debug.status.maxbytes": "Max bytes: ",
|
|
||||||
"crossmod.command.debug.status.logfile": "Log to file: ",
|
|
||||||
"crossmod.command.debug.enabled": "Enabled",
|
|
||||||
"crossmod.command.debug.disabled": "Disabled",
|
|
||||||
"crossmod.command.debug.enabled_short": "§aEnabled",
|
|
||||||
"crossmod.command.debug.disabled_short": "§cDisabled"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,12 +2,8 @@
|
||||||
"ltd.mod.client.name.trans_server": "LTD跨服传送模组",
|
"ltd.mod.client.name.trans_server": "LTD跨服传送模组",
|
||||||
"ltd.mod.client.key": "打开LTD跨服传送菜单",
|
"ltd.mod.client.key": "打开LTD跨服传送菜单",
|
||||||
"ltd.mod.client.menu": "跨服菜单",
|
"ltd.mod.client.menu": "跨服菜单",
|
||||||
"ltd.mod.client.menu.button.hub": "H 主城",
|
"ltd.mod.client.menu.button.1": "H 主城",
|
||||||
"ltd.mod.client.menu.button.survival": "S 生存服",
|
"ltd.mod.client.menu.button.2": "S 生存服",
|
||||||
"ltd.mod.client.menu.button.skyblock": "S 空岛服",
|
|
||||||
"ltd.mod.client.menu.button.resource": "R 资源服",
|
|
||||||
"ltd.mod.client.menu.button.minigame": "M 小游戏服",
|
|
||||||
"ltd.mod.client.menu.search": "搜索服务器..",
|
|
||||||
"ltd.mod.client.menu.checkbox.show_trans_tip": "显示传送提示",
|
"ltd.mod.client.menu.checkbox.show_trans_tip": "显示传送提示",
|
||||||
"ltd.mod.client.menu.checkbox.show_ping_stat": "显示Ping状态",
|
"ltd.mod.client.menu.checkbox.show_ping_stat": "显示Ping状态",
|
||||||
"ltd.mod.client.negotiating": "重定向中 ...",
|
"ltd.mod.client.negotiating": "重定向中 ...",
|
||||||
|
|
@ -40,26 +36,5 @@
|
||||||
"ltd.mod.ping.stats.avg_latency": "平均时延: %.1fms",
|
"ltd.mod.ping.stats.avg_latency": "平均时延: %.1fms",
|
||||||
"ltd.mod.ping.stats.packet_loss": "丢包率: %.1f%%",
|
"ltd.mod.ping.stats.packet_loss": "丢包率: %.1f%%",
|
||||||
"ltd.mod.ping.stats.sample_count": "样本数量: %d",
|
"ltd.mod.ping.stats.sample_count": "样本数量: %d",
|
||||||
"ltd.mod.ping.warn.network_latency": "网络延迟较高,建议减少Ping频率",
|
"ltd.mod.ping.warn.network_latency": "网络延迟较高,建议减少Ping频率"
|
||||||
"ltd.mod.client.menu.button.no_servers": "没有服务器",
|
|
||||||
"ltd.mod.client.overlay.tip": "按 [%s] 打开跨服传送菜单",
|
|
||||||
"ltd.mod.client.menu.command.header": "[=== LTD 跨服传送菜单 ===]",
|
|
||||||
"ltd.mod.client.menu.command.hover": "点击传送到 %s",
|
|
||||||
"crossmod.command.debug.packets.enabled": "[CrossMod] 数据包调试已开启",
|
|
||||||
"crossmod.command.debug.packets.disabled": "[CrossMod] 数据包调试已关闭",
|
|
||||||
"crossmod.command.debug.bundledebug.enabled": "[CrossMod] 捆绑包调试已开启",
|
|
||||||
"crossmod.command.debug.bundledebug.disabled": "[CrossMod] 捆绑包调试已关闭",
|
|
||||||
"crossmod.command.debug.status.title": "[CrossMod] 调试状态:",
|
|
||||||
"crossmod.command.debug.status.packet": "数据包调试: ",
|
|
||||||
"crossmod.command.debug.status.bundledebug": "捆绑包调试: ",
|
|
||||||
"crossmod.command.debug.status.maxbytes": "最大字节数: ",
|
|
||||||
"crossmod.command.debug.status.logfile": "日志输出到文件: ",
|
|
||||||
"crossmod.command.debug.enabled": "已开启",
|
|
||||||
"crossmod.command.debug.disabled": "已关闭",
|
|
||||||
"crossmod.command.debug.enabled_short": "§a已开启",
|
|
||||||
"crossmod.command.debug.disabled_short": "§c已关闭",
|
|
||||||
"ltd.mod.client.menu.button.1": "H 大厅",
|
|
||||||
"ltd.mod.client.menu.button.2": "S 生存",
|
|
||||||
"ltd.mod.request.goto": "请求跳转 %s",
|
|
||||||
"ltd.mod.client.menu.button.custom": "{icon} ${name}"
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,12 +6,8 @@
|
||||||
"refmap": "ltdcrossteleport.refmap.json",
|
"refmap": "ltdcrossteleport.refmap.json",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"AccessorMinecraft",
|
"AccessorMinecraft",
|
||||||
"MixinConnection",
|
|
||||||
"MixinMUINetWorkHandler",
|
"MixinMUINetWorkHandler",
|
||||||
"MixinPacketDecoder",
|
"ModListSpoofMixin"
|
||||||
"MixinPlayerList",
|
|
||||||
"ModListSpoofMixin",
|
|
||||||
"MixinBundlePacket"
|
|
||||||
],
|
],
|
||||||
"minVersion": "0.8",
|
"minVersion": "0.8",
|
||||||
"injectors": {
|
"injectors": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||||
id("xyz.jpenilla.run-velocity") version "2.3.1"
|
id("xyz.jpenilla.run-velocity") version "2.3.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user