Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5e46b3ee9 | |||
| 790c55c4c0 |
204
.idea/intellij-javadocs-4.0.1.xml
Normal file
204
.idea/intellij-javadocs-4.0.1.xml
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
<?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>
|
||||||
|
|
@ -59,12 +59,21 @@ 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 {
|
||||||
|
|
@ -97,11 +106,7 @@ 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'
|
||||||
|
|
||||||
modImplementation "curse.maven:easy-villagers-400514:3887794"
|
modCompileOnly "curse.maven:modern-ui-352491:6199942"
|
||||||
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.18.2
|
neoForge.parchment.minecraftVersion=1.20.1
|
||||||
neoForge.parchment.mappingsVersion=2022.11.06
|
neoForge.parchment.mappingsVersion=2023.09.03
|
||||||
|
|
||||||
## 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.18.2
|
minecraft_version=1.20.1
|
||||||
# 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.18.2,1.19)
|
minecraft_version_range=[1.20.1,1.21)
|
||||||
# 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=40.3.0
|
forge_version=47.3.4
|
||||||
# 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=[40,)
|
forge_version_range=[47,)
|
||||||
# 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=[40,)
|
loader_version_range=[47,)
|
||||||
# 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=[40,)
|
||||||
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=2022.11.06-1.18.2
|
mapping_version=2023.09.03-1.20.1
|
||||||
mapping_lasting_version=2022.11.06
|
mapping_lasting_version=2023.09.03
|
||||||
# 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=0.0.1.3
|
mod_version=1.0.0
|
||||||
# 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,13 +1,18 @@
|
||||||
package com.leisuretimedock.crossmod;
|
package com.leisuretimedock.crossmod;
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
|
@ -16,6 +21,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.IExtensionPoint;
|
import net.minecraftforge.fml.IExtensionPoint;
|
||||||
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;
|
||||||
|
|
@ -26,16 +33,16 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
@ -46,6 +53,7 @@ public class CrossTeleportMod {
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||||
PingCommand.register(event.getDispatcher());
|
PingCommand.register(event.getDispatcher());
|
||||||
|
GotoServerCommand.register(event.getDispatcher());
|
||||||
}
|
}
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
||||||
|
|
@ -53,6 +61,16 @@ public class CrossTeleportMod {
|
||||||
PingRequestManager.monitor(player);
|
PingRequestManager.monitor(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static int tickCounter = 0;
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||||
|
if (event.phase == TickEvent.Phase.END) {
|
||||||
|
tickCounter++;
|
||||||
|
if (tickCounter % 10 == 0) {
|
||||||
|
CrossServerConfigManager.INSTANCE.broadHashPacket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
|
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||||
if (event.getEntity() instanceof ServerPlayer player) {
|
if (event.getEntity() instanceof ServerPlayer player) {
|
||||||
|
|
@ -65,6 +83,7 @@ public class CrossTeleportMod {
|
||||||
}
|
}
|
||||||
@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) {
|
||||||
|
|
@ -87,4 +106,43 @@ 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.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.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.Unloading event) {
|
||||||
|
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
|
||||||
|
CrossServerConfigManager.unloading(CrossServerConfigManager.INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,21 @@ 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.ClientRegistry;
|
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||||
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 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 onRegisterKey(FMLClientSetupEvent event) {
|
public static void onRegisterKeyMappingsEvent (RegisterKeyMappingsEvent event) {
|
||||||
event.enqueueWork(() -> ClientRegistry.registerKeyBinding(OPEN_GUI_KEY));
|
event.register(OPEN_GUI_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
|
||||||
|
|
|
||||||
|
|
@ -27,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.LoggedInEvent event) {
|
public static void onLogin(ClientPlayerNetworkEvent.LoggingIn event) {
|
||||||
log.debug("[CrossTeleportMod] 玩家登录事件触发");
|
log.debug("[CrossTeleportMod] 玩家登录事件触发");
|
||||||
if (ClientResetManager.isNegotiating.get())
|
if (ClientResetManager.isNegotiating.get())
|
||||||
ClientResetManager.isNegotiating.set(false);
|
ClientResetManager.isNegotiating.set(false);
|
||||||
|
|
@ -87,7 +87,7 @@ public class PluginChannelClient {
|
||||||
|
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onLogout(ClientPlayerNetworkEvent.LoggedOutEvent event) {
|
public static void onLogout(ClientPlayerNetworkEvent.LoggingOut event) {
|
||||||
log.debug("[CrossTeleportMod] 玩家注销事件触发");
|
log.debug("[CrossTeleportMod] 玩家注销事件触发");
|
||||||
|
|
||||||
Connection connection = event.getConnection();
|
Connection connection = event.getConnection();
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ 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.TranslatableComponent;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public class GotoCommand {
|
public class GotoCommand {
|
||||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
|
|
@ -16,7 +16,7 @@ public class GotoCommand {
|
||||||
String server = StringArgumentType.getString(ctx, "server");
|
String server = StringArgumentType.getString(ctx, "server");
|
||||||
NetworkHandler.sendTeleportRequest(server);
|
NetworkHandler.sendTeleportRequest(server);
|
||||||
ctx.getSource().sendSuccess(
|
ctx.getSource().sendSuccess(
|
||||||
new TranslatableComponent("ltd.mod.client.request.goto",server), false);
|
() -> Component.translatable("ltd.mod.client.request.goto",server), false);
|
||||||
return 1;
|
return 1;
|
||||||
}));
|
}));
|
||||||
dispatcher.register(main);
|
dispatcher.register(main);
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,58 @@
|
||||||
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.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.TranslatableComponent;
|
import net.minecraft.network.chat.Component;
|
||||||
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 org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
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 Checkbox enableCrCheckBox;
|
||||||
|
private Checkbox enablePiCheckBox;
|
||||||
|
private Button closeButton;
|
||||||
|
private ServerSelectionList serverList;
|
||||||
|
|
||||||
public CrossServerGui() {
|
public CrossServerGui() {
|
||||||
super(new TranslatableComponent("ltd.mod.client.menu"));
|
super(TITLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
|
// 先添加按钮和复选框(它们应该渲染在列表上方)
|
||||||
|
initButtons();
|
||||||
|
|
||||||
|
// 再添加列表(它应该渲染在下方)
|
||||||
|
initServerList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initButtons() {
|
||||||
int centerX = width / 2;
|
int centerX = width / 2;
|
||||||
int centerY = height / 2;
|
int bottomY = height - 60; // 从底部向上60像素
|
||||||
int buttonWidth = 150;
|
|
||||||
int buttonHeight = 20;
|
|
||||||
int spacing = 5;
|
|
||||||
|
|
||||||
addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY - buttonHeight - spacing,
|
// 添加 Checkbox 控件 - 显示传送提示
|
||||||
buttonWidth, buttonHeight, new TranslatableComponent("ltd.mod.client.menu.button.1"), btn -> {
|
enableCrCheckBox = new Checkbox(centerX - 150, bottomY,
|
||||||
sendCustomPayload("connect:lobby");
|
140, 20, Component.translatable("ltd.mod.client.menu.checkbox.show_trans_tip"),
|
||||||
onClose();
|
!CrossServerTipOverLay.isShowOverlay()) {
|
||||||
}));
|
|
||||||
|
|
||||||
addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY,
|
|
||||||
buttonWidth, buttonHeight, new TranslatableComponent("ltd.mod.client.menu.button.2"), btn -> {
|
|
||||||
sendCustomPayload("connect:survival");
|
|
||||||
onClose();
|
|
||||||
}));
|
|
||||||
// 添加 Checkbox 控件
|
|
||||||
Checkbox enableCrCheckBox = new Checkbox(centerX - buttonWidth / 2, centerY + buttonHeight + spacing + 5,
|
|
||||||
150, 20, new TranslatableComponent("ltd.mod.client.menu.checkbox.show_trans_tip"), !CrossServerTipOverLay.isShowOverlay()) {
|
|
||||||
@Override
|
@Override
|
||||||
public void onPress() {
|
public void onPress() {
|
||||||
super.onPress();
|
super.onPress();
|
||||||
|
|
@ -58,9 +60,11 @@ public class CrossServerGui extends Screen {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
addRenderableWidget(enableCrCheckBox);
|
addRenderableWidget(enableCrCheckBox);
|
||||||
// 添加 Checkbox 控件
|
|
||||||
Checkbox enablePiCheckBox = new Checkbox(centerX - buttonWidth / 2, centerY + buttonHeight + spacing + 25,
|
// 添加 Checkbox 控件 - 显示ping统计
|
||||||
150, 20, new TranslatableComponent("ltd.mod.client.menu.checkbox.show_ping_stat"), !PingOverlayManager.isShowOverlay()) {
|
enablePiCheckBox = new Checkbox(centerX + 10, bottomY,
|
||||||
|
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();
|
||||||
|
|
@ -68,9 +72,89 @@ 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 sendCustomPayload(String message) {
|
private void initServerList() {
|
||||||
|
int screenWidth = this.width;
|
||||||
|
int screenHeight = this.height;
|
||||||
|
|
||||||
|
|
||||||
|
// 创建服务器列表,但尺寸要避开按钮区域
|
||||||
|
serverList = new ServerSelectionList(
|
||||||
|
this,
|
||||||
|
Minecraft.getInstance(),
|
||||||
|
screenWidth,
|
||||||
|
screenHeight,
|
||||||
|
48, // X位置
|
||||||
|
height - 64, // Y位置
|
||||||
|
36, // 条目高度
|
||||||
|
CrossServerConfigManager.INSTANCE.getServers()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置列表属性
|
||||||
|
serverList.setRenderBackground(true);
|
||||||
|
serverList.setRenderTopAndBottom(true);
|
||||||
|
|
||||||
|
|
||||||
|
addRenderableWidget(serverList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后渲染按钮和复选框(在上层)
|
||||||
|
// 手动调用按钮的render方法,确保它们在最上面
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
renderLogo(guiGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
// 先检查按钮点击
|
||||||
|
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 (serverList != null && serverList.mouseClicked(mouseX, mouseY, button)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.mouseClicked(mouseX, mouseY, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
@ -79,34 +163,13 @@ public class CrossServerGui extends Screen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void renderLogo(GuiGraphics guiGraphics) {
|
||||||
public void render(@NotNull PoseStack poseStack, int mouseX, int mouseY, float partialTicks) {
|
int logoWidth = 64; // 缩小Logo,为列表腾出空间
|
||||||
// 背景
|
int logoHeight = 64;
|
||||||
this.renderBackground(poseStack);
|
|
||||||
|
|
||||||
// Logo 渲染(缩放绘制)
|
int x = (this.width - logoWidth - font.width(this.title.getString()) * 2) / 2;
|
||||||
renderLogo(poseStack);
|
int y = -5; // 更靠近顶部
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package com.leisuretimedock.crossmod.client.gui;
|
package com.leisuretimedock.crossmod.client.gui;
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
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;
|
||||||
|
|
@ -26,32 +24,24 @@ public class GenericIceMessageScreen extends GenericDirtMessageScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(@NotNull PoseStack poseStack, int mouseX, int mouseY, float partialTick) {
|
public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
||||||
super.render(poseStack, mouseX, mouseY, partialTick);
|
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||||
if (minecraft != null && minecraft.player != null){
|
if (minecraft != null && minecraft.player != null){
|
||||||
InventoryScreen.renderEntityInInventory(width / 2, height / 2, 30, (float) width / 2 - mouseX, (float) height / 2 - mouseY, minecraft.player);
|
InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, width / 2, height / 2, 30, (float) width / 2 - mouseX, (float) height / 2 - mouseY, minecraft.player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderDirtBackground(int vOffset) {
|
public void renderDirtBackground(@NotNull GuiGraphics guiGraphics) {
|
||||||
renderIceBackground(vOffset,200, 200, 200, 255);
|
renderIceBackground(guiGraphics,1.0F, 1.0F, 1.0F, 1.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renderIceBackground(int vOffset, int r, int g, int b, int a) {
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
Tesselator tesselator = Tesselator.getInstance();
|
public void renderIceBackground(GuiGraphics guiGraphics, float r, float g, float b, float a) {
|
||||||
BufferBuilder bufferbuilder = tesselator.getBuilder();
|
guiGraphics.setColor(0.65F, 0.65F, 0.65F, 1.0F);
|
||||||
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
|
guiGraphics.blit(ICE, 0, 0, 0, 0.0F, 0.0F, this.width, this.height, 32, 32);
|
||||||
RenderSystem.setShaderTexture(0, ICE);
|
guiGraphics.setColor(r, g, b, a);
|
||||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
MinecraftForge.EVENT_BUS.post(new ScreenEvent.BackgroundRendered(this, guiGraphics));
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.leisuretimedock.crossmod.client.gui;
|
||||||
|
|
||||||
|
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.Map;
|
||||||
|
|
||||||
|
public class ServerSelectionList extends ObjectSelectionList<ServerSelectionList.ServerEntry> {
|
||||||
|
private final CrossServerGui parentScreen;
|
||||||
|
|
||||||
|
public ServerSelectionList(CrossServerGui parent, Minecraft mc, int width, int height, int y0, int y1, int itemHeight, Map<String, String> servers) {
|
||||||
|
super(mc, width, height, y0, y1, itemHeight);
|
||||||
|
this.parentScreen = parent;
|
||||||
|
|
||||||
|
// 添加服务器条目
|
||||||
|
if (servers.isEmpty()) {
|
||||||
|
this.addEntry(new ServerEntry(Component.translatable("ltd.mod.client.menu.button.no_servers"), null, parentScreen));
|
||||||
|
} else {
|
||||||
|
servers.forEach((server_name, translate_key) -> {
|
||||||
|
this.addEntry(new ServerEntry(Component.translatable(translate_key), server_name, parentScreen));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.GuiComponent;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.renderer.entity.ItemRenderer;
|
import net.minecraft.network.chat.Component;
|
||||||
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.ForgeIngameGui;
|
import net.minecraftforge.client.gui.overlay.ForgeGui;
|
||||||
import net.minecraftforge.client.gui.IIngameOverlay;
|
import net.minecraftforge.client.gui.overlay.IGuiOverlay;
|
||||||
|
|
||||||
public class CrossServerTipOverLay implements IIngameOverlay {
|
public class CrossServerTipOverLay implements IGuiOverlay {
|
||||||
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,24 +22,22 @@ public class CrossServerTipOverLay implements IIngameOverlay {
|
||||||
showOverlay = show;
|
showOverlay = show;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void render(ForgeIngameGui forgeIngameGui, PoseStack poseStack, float v, int i, int i1) {
|
public void render(ForgeGui forgeGui, GuiGraphics guiGraphics, float v, int i, int i1) {
|
||||||
if ( !showOverlay || mc.player == null || mc.level == null) return;
|
if (isShowOverlay()) 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();
|
||||||
// 2. 渲染钟图标(含动画帧)
|
poseStack.translate(10, 10, 10);
|
||||||
itemRenderer.renderAndDecorateItem(clockStack, x, y);
|
// 2. 渲染钟图标
|
||||||
itemRenderer.renderGuiItemDecorations(mc.font, clockStack, x, y);
|
guiGraphics.renderItem(clockStack, x, y);
|
||||||
|
guiGraphics.renderItemDecorations(font,clockStack, x, y);
|
||||||
// 3. 绘制提示文字
|
// 3. 绘制提示文字
|
||||||
String keyText = KeyBindingHandler.OPEN_GUI_KEY.getTranslatedKeyMessage().getString(); // 可动态从 KeyMapping 获取
|
String text = Component.translatable("ltd.mod.client.overlay.tip", KeyBindingHandler.OPEN_GUI_KEY.getTranslatedKeyMessage()).getString();
|
||||||
String text = "按 [" + keyText.toUpperCase() + "] 打开跨服传送菜单";
|
guiGraphics.drawString(font, text, x + 20, y + 6, 0xFFFFFF);
|
||||||
GuiComponent.drawString(poseStack, font, text, x + 20, y + 6, 0xFFFFFF);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,20 @@ 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.gui.OverlayRegistry;
|
import net.minecraftforge.client.event.RegisterGuiOverlaysEvent;
|
||||||
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 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(FMLClientSetupEvent event) {
|
public static void onRender(RegisterGuiOverlaysEvent event) {
|
||||||
event.enqueueWork(() -> {
|
event.registerAboveAll("cross_server_tip", CrossServerTipOverLay.INSTANCE);
|
||||||
OverlayRegistry.registerOverlayTop(
|
event.registerAboveAll(
|
||||||
"cross_server_tip",
|
"ping_debug",
|
||||||
CrossServerTipOverLay.INSTANCE
|
PingOverlayManager.INSTANCE
|
||||||
);
|
);
|
||||||
OverlayRegistry.registerOverlayTop(
|
|
||||||
"ping_debug",
|
|
||||||
PingOverlayManager.INSTANCE
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
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.GuiComponent;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraftforge.client.gui.ForgeIngameGui;
|
import net.minecraftforge.client.gui.overlay.ForgeGui;
|
||||||
import net.minecraftforge.client.gui.IIngameOverlay;
|
import net.minecraftforge.client.gui.overlay.IGuiOverlay;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PingOverlayManager implements IIngameOverlay {
|
public class PingOverlayManager implements IGuiOverlay {
|
||||||
public static final PingOverlayManager INSTANCE = new PingOverlayManager();
|
public static final PingOverlayManager INSTANCE = new PingOverlayManager();
|
||||||
private static boolean showOverlay = true;
|
private static boolean showOverlay = false;
|
||||||
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 +28,7 @@ public class PingOverlayManager implements IIngameOverlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(ForgeIngameGui gui, PoseStack poseStack, float partialTick, int width, int height) {
|
public void render(ForgeGui gui, GuiGraphics guiGraphics, 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,10 +49,10 @@ public class PingOverlayManager implements IIngameOverlay {
|
||||||
int y = findSuitableYPosition(height, totalHeight);
|
int y = findSuitableYPosition(height, totalHeight);
|
||||||
|
|
||||||
// 绘制背景
|
// 绘制背景
|
||||||
drawBackground(poseStack, x, y, maxWidth, totalHeight, font);
|
drawBackground(guiGraphics, x, y, maxWidth, totalHeight, font);
|
||||||
|
|
||||||
// 绘制文本
|
// 绘制文本
|
||||||
drawTextLines(gui, poseStack, font, allLines, x, y);
|
drawTextLines(guiGraphics, font, allLines, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getAllDisplayLines() {
|
private List<String> getAllDisplayLines() {
|
||||||
|
|
@ -95,18 +94,18 @@ public class PingOverlayManager implements IIngameOverlay {
|
||||||
return baseY;
|
return baseY;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawBackground(PoseStack poseStack, int x, int y, int width, int height, Font font) {
|
private void drawBackground(GuiGraphics guiGraphics, int x, int y, int width, int height, Font font) {
|
||||||
GuiComponent.fill(poseStack,
|
guiGraphics.fill(
|
||||||
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(ForgeIngameGui gui, PoseStack poseStack, Font font, List<String> lines, int x, int y) {
|
private void drawTextLines(GuiGraphics guiGraphics, 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()) {
|
||||||
gui.getFont().draw(poseStack, line,
|
guiGraphics.drawString(font, line,
|
||||||
x,
|
x,
|
||||||
y + i * font.lineHeight,
|
y + i * font.lineHeight,
|
||||||
TEXT_COLOR);
|
TEXT_COLOR);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
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 java.util.Collection;
|
||||||
|
|
||||||
|
public class GotoServerCommand {
|
||||||
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
|
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("cross")
|
||||||
|
.requires(cs -> cs.hasPermission(2))
|
||||||
|
.then(Commands.argument("players", EntityArgument.players())
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
source.sendFailure(Component.literal("Request a player"));
|
||||||
|
return 1;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
dispatcher.register(main);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ 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.TranslatableComponent;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
@ -92,7 +92,7 @@ public class PingCommand {
|
||||||
|
|
||||||
private static int executePlayerReport(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
private static int executePlayerReport(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,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(new TranslatableComponent("ltd.mod.ping.info.no_data"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ public class PingCommand {
|
||||||
Map<UUID, Long> results = PingRequestManager.getAllLatestPings();
|
Map<UUID, Long> results = PingRequestManager.getAllLatestPings();
|
||||||
|
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,7 +134,7 @@ public class PingCommand {
|
||||||
PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats();
|
PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats();
|
||||||
|
|
||||||
if (stats.sampleCount() == 0) {
|
if (stats.sampleCount() == 0) {
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,28 +147,28 @@ public class PingCommand {
|
||||||
private static int executeSinglePing(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(new TranslatableComponent("ltd.mod.ping.error.not_monitored.self"));
|
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.self"));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
PingRequestManager.ping(player);
|
PingRequestManager.ping(player);
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_self"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_self"), false);
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executePingPlayers(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
private static int executePingPlayers(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(() -> Component.translatable("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(new TranslatableComponent("ltd.mod.ping.error.not_monitored.other",
|
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.other",
|
||||||
player.getScoreboardName()));
|
player.getScoreboardName()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PingRequestManager.ping(player);
|
PingRequestManager.ping(player);
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_other",
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_other",
|
||||||
player.getScoreboardName()), false);
|
player.getScoreboardName()), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -180,7 +180,7 @@ public class PingCommand {
|
||||||
int count,
|
int count,
|
||||||
int interval) {
|
int interval) {
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,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()) ?
|
||||||
new TranslatableComponent("ltd.mod.ping.success.multiping.start.self", count, interval) :
|
() -> Component.translatable("ltd.mod.ping.success.multiping.start.self", count, interval) :
|
||||||
new TranslatableComponent("ltd.mod.ping.success.multiping.start.other", player.getScoreboardName(), count, interval),
|
() -> Component.translatable("ltd.mod.ping.success.multiping.start.other", player.getScoreboardName(), count, interval),
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
source.sendFailure(
|
source.sendFailure(
|
||||||
new TranslatableComponent(
|
Component.translatable(
|
||||||
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",
|
||||||
|
|
@ -209,28 +209,28 @@ public class PingCommand {
|
||||||
ServerPlayer player = source.getPlayerOrException();
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
if (monitor) {
|
if (monitor) {
|
||||||
PingRequestManager.monitor(player);
|
PingRequestManager.monitor(player);
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.monitor.self"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.monitor.self"), false);
|
||||||
} else {
|
} else {
|
||||||
PingRequestManager.unmonitor(player);
|
PingRequestManager.unmonitor(player);
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.unmonitor.self"), false);
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.unmonitor.self"), false);
|
||||||
}
|
}
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int executeToggleMonitoring(CommandSourceStack source, 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(new TranslatableComponent("ltd.mod.ping.error.no_players"), false);
|
source.sendSuccess(() -> Component.translatable("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(new TranslatableComponent("ltd.mod.ping.error.not_monitored.other",
|
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.other",
|
||||||
player.getScoreboardName()));
|
player.getScoreboardName()));
|
||||||
} else {
|
} else {
|
||||||
PingRequestManager.unmonitor(player);
|
PingRequestManager.unmonitor(player);
|
||||||
source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_other",
|
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_other",
|
||||||
player.getScoreboardName()), false);
|
player.getScoreboardName()), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -239,37 +239,37 @@ public class PingCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendTextReport(ServerPlayer player, Map<UUID, Long> results) {
|
private static void sendTextReport(ServerPlayer player, Map<UUID, Long> results) {
|
||||||
player.sendMessage(new TranslatableComponent("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD),
|
player.displayClientMessage(Component.translatable("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD),
|
||||||
player.getUUID());
|
true);
|
||||||
|
|
||||||
results.forEach((uuid, ping) -> {
|
results.forEach((uuid, ping) -> {
|
||||||
player.sendMessage(
|
player.displayClientMessage(
|
||||||
new TranslatableComponent(
|
Component.translatable(
|
||||||
"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)),
|
||||||
player.getUUID()
|
true
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendStatsTextReport(ServerPlayer player, PingRequestManager.PingStats stats) {
|
private static void sendStatsTextReport(ServerPlayer player, PingRequestManager.PingStats stats) {
|
||||||
player.sendMessage(new TranslatableComponent("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD),
|
player.displayClientMessage(Component.translatable("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD),
|
||||||
player.getUUID());
|
true);
|
||||||
player.sendMessage(new TranslatableComponent(
|
player.displayClientMessage(Component.translatable(
|
||||||
"ltd.mod.ping.stats.average", stats.average()), player.getUUID());
|
"ltd.mod.ping.stats.average", stats.average()), true);
|
||||||
player.sendMessage(new TranslatableComponent(
|
player.displayClientMessage(Component.translatable(
|
||||||
"ltd.mod.ping.stats.max", stats.max()), player.getUUID());
|
"ltd.mod.ping.stats.max", stats.max()), true);
|
||||||
player.sendMessage(new TranslatableComponent(
|
player.displayClientMessage(Component.translatable(
|
||||||
"ltd.mod.ping.stats.min", stats.max()), player.getUUID());
|
"ltd.mod.ping.stats.min", stats.max()), true);
|
||||||
player.sendMessage(new TranslatableComponent(
|
player.displayClientMessage(Component.translatable(
|
||||||
"ltd.mod.ping.stats.avg_latency", stats.averageLatency()), player.getUUID());
|
"ltd.mod.ping.stats.avg_latency", stats.averageLatency()), true);
|
||||||
player.sendMessage(new TranslatableComponent(
|
player.displayClientMessage(Component.translatable(
|
||||||
"ltd.mod.ping.stats.packet_loss", stats.packetLossRate()), player.getUUID());
|
"ltd.mod.ping.stats.packet_loss", stats.packetLossRate()), true);
|
||||||
player.sendMessage(new TranslatableComponent(
|
player.displayClientMessage(Component.translatable(
|
||||||
"ltd.mod.ping.stats.sample_count", stats.sampleCount()), player.getUUID());
|
"ltd.mod.ping.stats.sample_count", stats.sampleCount()), true);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.leisuretimedock.crossmod.config;
|
||||||
|
|
||||||
|
import net.minecraftforge.common.ForgeConfigSpec;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
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;
|
||||||
|
static {
|
||||||
|
BUILDER.comment("Cross Server Config").push("servers");
|
||||||
|
SERVER_LIST = BUILDER
|
||||||
|
.comment("Server list in format: <server_name>: <translate_key>")
|
||||||
|
.defineList("serverList",
|
||||||
|
Arrays.asList(
|
||||||
|
"lobby: ltd.mod.client.menu.button.1",
|
||||||
|
"survival: ltd.mod.client.menu.button.2"
|
||||||
|
),
|
||||||
|
obj -> obj instanceof String str && checkSyntax(str)
|
||||||
|
);
|
||||||
|
|
||||||
|
BUILDER.pop();
|
||||||
|
SPEC = BUILDER.build();
|
||||||
|
}
|
||||||
|
public static boolean checkSyntax(@NotNull String input) {
|
||||||
|
return CrossServerConfigManager.SYNTAX.matcher(input).matches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
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();
|
||||||
|
public static final Pattern SYNTAX =
|
||||||
|
compile("([a-zA-Z]\\w+):\\s+([_.\\w]+)");
|
||||||
|
/**
|
||||||
|
* 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<>();
|
||||||
|
|
||||||
|
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); // 第一部分:[a-zA-Z]\w+
|
||||||
|
String value = matcher.group(2); // 第二部分:[_.\w]+
|
||||||
|
if(!serverMap.containsKey(key)) {
|
||||||
|
serverMap.put(key, value);
|
||||||
|
} else {
|
||||||
|
log.warn("Duplicate server name '{}' found in config, skip it", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(serverMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadAll() {
|
||||||
|
try {
|
||||||
|
clear();
|
||||||
|
servers.putAll(parseServer(CrossServerConfig.SERVER_LIST.get()));
|
||||||
|
cacheHash = -1;
|
||||||
|
cacheTag = serializeToNBT();
|
||||||
|
log.debug("Configs reloaded");
|
||||||
|
} 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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
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.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.ModifyArg;
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
|
||||||
@Pseudo
|
@Pseudo
|
||||||
@Mixin(value = NetworkHandler.class, remap = false)
|
@Mixin(value = NetworkHandler.class, remap = false)
|
||||||
public class MixinMUINetWorkHandler {
|
public class MixinMUINetWorkHandler {
|
||||||
/**
|
|
||||||
* 修补构造 ResourceLocation("modernui", id) 时,若 id 是空字符串,则替换为 "default"
|
@ModifyVariable(
|
||||||
*/
|
|
||||||
@ModifyArg(
|
|
||||||
method = "<init>",
|
method = "<init>",
|
||||||
at = @At(
|
at = @At("HEAD"),
|
||||||
value = "INVOKE",
|
argsOnly = true,
|
||||||
target = "Lnet/minecraft/resources/ResourceLocation;<init>(Ljava/lang/String;Ljava/lang/String;)V"
|
ordinal = 0
|
||||||
),
|
|
||||||
index = 1 // 修改 id 参数
|
|
||||||
)
|
)
|
||||||
private String fixEmptyId(String id) {
|
private static ResourceLocation modifyNameParameter(ResourceLocation name) {
|
||||||
return id == null || id.isEmpty() ? "default" : id;
|
return name.getPath().isEmpty() ? name : new ResourceLocation(name.getNamespace(), "default");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// 客户端网络处理类(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.PingMessagePayload;
|
import com.leisuretimedock.crossmod.network.toClient.*;
|
||||||
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;
|
||||||
|
|
@ -17,6 +15,7 @@ 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.*;
|
||||||
|
|
||||||
|
|
@ -74,6 +73,26 @@ 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,
|
||||||
|
|
@ -88,7 +107,7 @@ public class NetworkHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void sendPingResults(ServerPlayer player, Map<UUID, Long> results) {
|
public static void sendPingResults(ServerPlayer player, @NotNull Map<UUID, Long> results) {
|
||||||
// 创建平均时延映射
|
// 创建平均时延映射
|
||||||
Map<UUID, Double> averageLatencies = new HashMap<>();
|
Map<UUID, Double> averageLatencies = new HashMap<>();
|
||||||
|
|
||||||
|
|
@ -116,7 +135,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.getConnection(),
|
player.connection.connection,
|
||||||
NetworkDirection.PLAY_TO_CLIENT);
|
NetworkDirection.PLAY_TO_CLIENT);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("发送ping请求失败", e);
|
log.error("发送ping请求失败", e);
|
||||||
|
|
@ -152,4 +171,37 @@ 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, PacketDistributor<T> packetDistributor){
|
||||||
|
CHANNEL.send(packetDistributor.with(() -> entity), message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,13 @@ 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.TranslatableComponent;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class PingRequestManager {
|
public final class PingRequestManager {
|
||||||
// 配置常量
|
// 配置常量
|
||||||
|
|
@ -104,8 +106,8 @@ public final class PingRequestManager {
|
||||||
|
|
||||||
// 网络拥塞检测
|
// 网络拥塞检测
|
||||||
if (ping > DEFAULT_TIMEOUT_MS * 0.8) {
|
if (ping > DEFAULT_TIMEOUT_MS * 0.8) {
|
||||||
player.sendMessage(new TranslatableComponent("ltd.mod.ping.warn.network_latency"),
|
player.displayClientMessage(Component.translatable("ltd.mod.ping.warn.network_latency", player.getUUID()),
|
||||||
player.getUUID());
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePingHistory(data, ping);
|
updatePingHistory(data, ping);
|
||||||
|
|
@ -175,7 +177,7 @@ public final class PingRequestManager {
|
||||||
int successfulRequests = 0;
|
int successfulRequests = 0;
|
||||||
|
|
||||||
for (PlayerPingData data : playerData.values()) {
|
for (PlayerPingData data : playerData.values()) {
|
||||||
synchronized (data) {
|
synchronized (playerData) {
|
||||||
allPings.addAll(data.pingHistory);
|
allPings.addAll(data.pingHistory);
|
||||||
totalRequests += data.totalRequests;
|
totalRequests += data.totalRequests;
|
||||||
successfulRequests += data.successfulRequests;
|
successfulRequests += data.successfulRequests;
|
||||||
|
|
@ -212,7 +214,7 @@ public final class PingRequestManager {
|
||||||
Map<UUID, Long> results = new HashMap<>();
|
Map<UUID, Long> results = new HashMap<>();
|
||||||
|
|
||||||
playerData.forEach((uuid, data) -> {
|
playerData.forEach((uuid, data) -> {
|
||||||
synchronized (data) {
|
synchronized (PingRequestManager.class) {
|
||||||
if (!data.pingHistory.isEmpty()) {
|
if (!data.pingHistory.isEmpty()) {
|
||||||
results.put(uuid, data.pingHistory.getLast());
|
results.put(uuid, data.pingHistory.getLast());
|
||||||
}
|
}
|
||||||
|
|
@ -281,12 +283,12 @@ public final class PingRequestManager {
|
||||||
|
|
||||||
if (attempt == count) {
|
if (attempt == count) {
|
||||||
data.batchInProgress = 0;
|
data.batchInProgress = 0;
|
||||||
player.sendMessage(
|
player.displayClientMessage(
|
||||||
new TranslatableComponent("ltd.mod.ping.success.multiping.complete", count),
|
Component.translatable("ltd.mod.ping.success.multiping.complete", count),
|
||||||
player.getUUID());
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, i * Math.max(intervalMs, MIN_PING_INTERVAL), TimeUnit.MILLISECONDS);
|
}, i * intervalMs, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -328,7 +330,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 (data) {
|
synchronized (PingRequestManager.class) {
|
||||||
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));
|
||||||
|
|
@ -348,7 +350,7 @@ public final class PingRequestManager {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
playerData.forEach((playerId, data) -> {
|
playerData.forEach((playerId, data) -> {
|
||||||
synchronized (data) {
|
synchronized (PingRequestManager.class) {
|
||||||
data.activeRequests.entrySet().removeIf(entry ->
|
data.activeRequests.entrySet().removeIf(entry ->
|
||||||
currentTime - entry.getValue() > DEFAULT_TIMEOUT_MS
|
currentTime - entry.getValue() > DEFAULT_TIMEOUT_MS
|
||||||
);
|
);
|
||||||
|
|
@ -364,29 +366,21 @@ public final class PingRequestManager {
|
||||||
|
|
||||||
|
|
||||||
private static double calculateAverageLatency(Collection<Long> pingHistory) {
|
private static double calculateAverageLatency(Collection<Long> pingHistory) {
|
||||||
if (pingHistory.isEmpty()) return 0;
|
return PlayerPingData.calculate(pingHistory);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double calculatePacketLossRate(PlayerPingData data) {
|
private static double calculatePacketLossRate(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 {
|
||||||
|
|
@ -406,6 +400,10 @@ public final class PingRequestManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateAverageLatency(Collection<Long> pings) {
|
private double calculateAverageLatency(Collection<Long> pings) {
|
||||||
|
return calculate(pings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double calculate(Collection<Long> pings) {
|
||||||
if (pings.isEmpty()) return 0;
|
if (pings.isEmpty()) return 0;
|
||||||
|
|
||||||
double total = 0;
|
double total = 0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
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 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(CommonConfigHashInformPacket packet, FriendlyByteBuf buffer) {
|
||||||
|
buffer.writeInt(packet.hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode common config hash inform packet.
|
||||||
|
*
|
||||||
|
* @param buffer the buffer
|
||||||
|
* @return the common config hash inform packet
|
||||||
|
*/
|
||||||
|
public static CommonConfigHashInformPacket decode(FriendlyByteBuf buffer) {
|
||||||
|
return new CommonConfigHashInformPacket(buffer.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle.
|
||||||
|
*
|
||||||
|
* @param packet the packet
|
||||||
|
* @param ctx the ctx
|
||||||
|
*/
|
||||||
|
public static void handle(CommonConfigHashInformPacket packet, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,11 +8,13 @@ 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.TranslatableComponent;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraftforge.network.*;
|
import net.minecraftforge.network.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
|
@ -32,18 +34,24 @@ 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(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(new TranslatableComponent("ltd.mod.client.invalid_packet"));
|
conn.disconnect(Component.translatable("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(), null, s -> {}
|
conn, Minecraft.getInstance(), serverData ,null, true, null, s -> {}
|
||||||
));
|
));
|
||||||
|
|
||||||
((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn);
|
((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn);
|
||||||
|
|
@ -55,11 +63,10 @@ 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(new TranslatableComponent("ltd.mod.client.error.handshake"));
|
conn.disconnect(Component.translatable("ltd.mod.client.error.handshake"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.setPacketHandled(true);
|
ctx.setPacketHandled(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
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 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(SyncCommonConfigRequestPacket msg, FriendlyByteBuf buf) {
|
||||||
|
buf.writeInt(msg.hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode sync common config request packet.
|
||||||
|
*
|
||||||
|
* @param buf the buf
|
||||||
|
* @return the sync common config request packet
|
||||||
|
*/
|
||||||
|
public static SyncCommonConfigRequestPacket decode(FriendlyByteBuf buf) {
|
||||||
|
return new SyncCommonConfigRequestPacket(buf.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle.
|
||||||
|
*
|
||||||
|
* @param msg the msg
|
||||||
|
* @param ctx the ctx
|
||||||
|
*/
|
||||||
|
public static void handle(SyncCommonConfigRequestPacket msg, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,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)
|
||||||
.consumer(HandshakeHandler.biConsumerFor(ResetPacket::handler))
|
.consumerMainThread(HandshakeHandler.biConsumerFor(ResetPacket::handler))
|
||||||
.add();
|
.add();
|
||||||
log.info( "Registered forge reset packet successfully.");
|
log.info( "Registered forge reset packet successfully.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ 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.client.multiplayer.ServerData;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.network.chat.TranslatableComponent;
|
import net.minecraft.server.packs.repository.Pack;
|
||||||
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 java.util.NoSuchElementException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
|
@ -18,17 +19,26 @@ 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.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();
|
||||||
ServerData serverData = minecraft.getCurrentServer();
|
Pack serverPack = Minecraft.getInstance().getDownloadedPackSource().serverPack;
|
||||||
if (minecraft.level == null) {
|
if (minecraft.level == null) {
|
||||||
GameData.revertToFrozen();
|
GameData.revertToFrozen();
|
||||||
}
|
}
|
||||||
|
Minecraft.getInstance().getDownloadedPackSource().serverPack = null;
|
||||||
minecraft.clearLevel(new GenericIceMessageScreen(new TranslatableComponent("ltd.mod.client.negotiating").withStyle(BOLD)));
|
minecraft.clearLevel(new GenericIceMessageScreen(Component.translatable("ltd.mod.client.negotiating").withStyle(BOLD)));
|
||||||
minecraft.setCurrentServer(serverData);
|
try {
|
||||||
|
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 {
|
||||||
|
|
@ -37,7 +47,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(new TranslatableComponent("ltd.mod.client.failed.reset_connection"));
|
Objects.requireNonNull(Minecraft.getInstance().getConnection()).onDisconnect(Component.translatable("ltd.mod.client.failed.reset_connection"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
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 java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
@ -30,33 +24,7 @@ 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) {
|
||||||
NetworkEvent.Context ctx = ctxSupplier.get();
|
com.leisuretimedock.crossmod.network.toClient.ResetPacket.handler(ctxSupplier, log);
|
||||||
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 +1,2 @@
|
||||||
public net.minecraft.client.Minecraft pendingConnection #pendingConnection
|
public net.minecraft.client.resources.DownloadedPackSource f_244082_ # serverPack
|
||||||
|
public net.minecraft.client.Minecraft f_91009_ # pendingConnection
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
"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",
|
||||||
|
|
|
||||||
|
|
@ -36,5 +36,7 @@
|
||||||
"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] 打开跨服传送菜单"
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user