Major update
Added configuration for delay and attempts Added commands to reload and change config Added support for 1.16 Added support for ModMenu and AuthMe Improved reconnecting logic to be more efficient and cleaner looking Improved countdown overlay
56
README.md
@ -1,20 +1,56 @@
|
||||
# AutoReconnect
|
||||
# AutoReconnect [1.16+][Fabric]
|
||||
|
||||
### Description
|
||||
This mod will automatically try to reconnect you back to a server if you got disconnected.
|
||||
By default, it will make 4 attempts after 3, 10, 30 and 60 seconds.
|
||||
|
||||
This mod allows you to afk without fear of getting disconnected. It will make 4 attempts to reconnect you to the server you were disconnected from, with a delay of 3, 10, 60 and 300 seconds.
|
||||
After 4 failed attempts it will no longer try to reconnect.
|
||||
### Features
|
||||
|
||||
<sub>(Btw you can exit the disconnect screen with <kbd>Esc</kbd>)</sub>
|
||||
* Multiple individually delayed reconnect attempts
|
||||
* Displays a countdown on the disconnect screen
|
||||
* Allows you to exit the disconnect screen quickly by pressing the escape key
|
||||
* Customizable
|
||||
* Amount of attempts
|
||||
* Delay between each attempt
|
||||
* Client side commands
|
||||
* `/autoreconnect reload` Reloads the config and displays the settings in chat
|
||||
* `/autoreconnect config [<delayList>]` Sets the delay between each attempt<br>
|
||||
`[<delayList>]` must be a Nbt List Tag containing Integers, e.g. `[3, 10, 30, 60]` or `[I;3, 10, 30, 60]`
|
||||
* Support for several mods
|
||||
|
||||
### Installation
|
||||
|
||||
1. [Download](https://fabricmc.net/use/) and install Fabric
|
||||
2. Download [Fabric API]() and put the jar file into the mods folder
|
||||
3. Do the same for this mod
|
||||
|
||||
### Compatibility
|
||||
|
||||
* [ModMenu](https://www.curseforge.com/minecraft/mc-mods/modmenu) <br>
|
||||
Properly shows the mod icon, name and author
|
||||
and provides a link for the curseforge project page and the github issues page
|
||||
* [AuthMe](https://www.curseforge.com/minecraft/mc-mods/auth-me) <br>
|
||||
Pauses the countdown if you click on the Re-authenticate button to revalidate the session of the game
|
||||
|
||||
### Common questions
|
||||
|
||||
* _Can I change the delay?_<br>
|
||||
Yes, you finally can with the latest version of this mod.
|
||||
* _Forge version?_<br>
|
||||
Simply no. I am not interested in developing mods using Forge.
|
||||
* _Version for 1.13.x or lower?_<br>
|
||||
Fabric does not exist for those versions.
|
||||
* _Version for 1.14.x or 1.15.x?_<br>
|
||||
If there is enough demand for it I might make a version for those versions of the game.
|
||||
|
||||
### Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
<sub>(Mod doesn't change the background or the font, I was using a resource pack when I took the screenshots)</sub>
|
||||

|
||||
|
||||
### License
|
||||
|
||||
This mod is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects.
|
||||
This mod is available under the CC0 license.
|
||||
Feel free to learn from it and incorporate it in your own projects.
|
||||
If you actually just copy code or use this mod in a mod pack I would appreciate it if you mention me
|
||||
by linking the github page or the curseforge project page.
|
||||
10
build.gradle
@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'fabric-loom' version '0.4-SNAPSHOT'
|
||||
id 'fabric-loom' version '0.5-SNAPSHOT'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
@ -11,12 +11,12 @@ version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.16.1"
|
||||
mappings "net.fabricmc:yarn:1.16.1+build.21:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.9.3+build.207"
|
||||
minecraft "com.mojang:minecraft:1.16"
|
||||
mappings "net.fabricmc:yarn:1.16+build.4:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.10.8"
|
||||
|
||||
//Fabric api
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:0.18.0+build.387-1.16.1"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:0.29.2+1.16"
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
||||
@ -3,15 +3,15 @@ org.gradle.jvmargs=-Xmx1G
|
||||
|
||||
# Fabric Properties
|
||||
# check these on https://fabricmc.net/use
|
||||
minecraft_version=1.16.1
|
||||
yarn_mappings=1.16.1+build.21
|
||||
loader_version=0.9.3+build.207
|
||||
minecraft_version=1.16
|
||||
yarn_mappings=1.16+build.4
|
||||
loader_version=0.10.8
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 1.0.1
|
||||
mod_version = 1.1.0
|
||||
maven_group = net.autoreconnect
|
||||
archives_base_name = auto-reconnect
|
||||
archives_base_name = autoreconnect
|
||||
|
||||
# Dependencies
|
||||
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
|
||||
fabric_version=0.18.0+build.387-1.16.1
|
||||
fabric_version=0.29.2+1.16
|
||||
@ -1,57 +1,126 @@
|
||||
package net.autoreconnect;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.network.ClientCommandSource;
|
||||
import net.minecraft.client.network.ServerInfo;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.util.Formatting;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
|
||||
import static net.autoreconnect.ClientCommands.*;
|
||||
import static net.autoreconnect.Util.*;
|
||||
import static net.minecraft.command.arguments.NbtTagArgumentType.nbtTag;
|
||||
import static net.minecraft.util.Formatting.*;
|
||||
|
||||
public class AutoReconnect implements ModInitializer
|
||||
{
|
||||
public static int attempt = 0;
|
||||
public static boolean connect = false;
|
||||
|
||||
private static final AtomicInteger countdown = new AtomicInteger();
|
||||
private static Timer timer = null;
|
||||
public static final String MOD_ID = "autoreconnect";
|
||||
public static int[] delayList = { 3, 10, 30, 60 };
|
||||
public static int ticks = -1;
|
||||
public static int attempt = -1;
|
||||
public static ServerInfo lastServerEntry = null;
|
||||
public static boolean pause = false;
|
||||
|
||||
@Override
|
||||
public void onInitialize() { }
|
||||
|
||||
public static void startCountdown(int seconds)
|
||||
public void onInitialize()
|
||||
{
|
||||
countdown.set(seconds);
|
||||
timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new TimerTask()
|
||||
ClientCommands.register(literal("reload").executes(AutoReconnect::cmdReload));
|
||||
ClientCommands.register(literal("config").then(argument("delayList", nbtTag()).executes(AutoReconnect::cmdConfig)));
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
private static int cmdReload(CommandContext<ClientCommandSource> ctx)
|
||||
{
|
||||
loadConfig();
|
||||
return SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private static int cmdConfig(CommandContext<ClientCommandSource> ctx)
|
||||
{
|
||||
Tag tag = ctx.getArgument("delayList", Tag.class);
|
||||
try
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
// if tag is not a list or a list not containing integers it will pass null or an empty list
|
||||
setDelayList(tag instanceof AbstractListTag ? ((AbstractListTag<? extends Tag>) tag).stream().filter(IntTag.class::isInstance).map(IntTag.class::cast).mapToInt(IntTag::getInt).toArray() : null);
|
||||
saveConfig();
|
||||
send(colored("Current configuration: " + Arrays.toString(delayList), GREEN));
|
||||
}
|
||||
catch (IOException | IllegalArgumentException ex)
|
||||
{
|
||||
send(err(ex));
|
||||
}
|
||||
return SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private static void loadConfig()
|
||||
{
|
||||
Path configPath = FabricLoader.getInstance().getConfigDir().resolve(MOD_ID + ".json");
|
||||
try
|
||||
{
|
||||
setDelayList(new Gson().fromJson(Files.newBufferedReader(configPath), int[].class));
|
||||
send(colored("Current configuration: " + Arrays.toString(delayList), GREEN));
|
||||
}
|
||||
catch (IOException | IllegalArgumentException | JsonParseException ex)
|
||||
{
|
||||
send(err(ex));
|
||||
try
|
||||
{
|
||||
if (countdown.decrementAndGet() <= 0)
|
||||
{
|
||||
connect = true;
|
||||
cancel();
|
||||
}
|
||||
send(colored("Creating default config...", GREEN));
|
||||
saveConfig();
|
||||
}
|
||||
}, 1000, 1000);
|
||||
catch (IOException ex2)
|
||||
{
|
||||
send(err(ex2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void cancel()
|
||||
private static void saveConfig() throws IOException
|
||||
{
|
||||
if (timer == null) return;
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
Path configPath = FabricLoader.getInstance().getConfigDir().resolve(MOD_ID + ".json");
|
||||
File configFile = configPath.toFile();
|
||||
// if file already exists or could successfully be created
|
||||
if (configFile.exists() || configFile.createNewFile())
|
||||
{
|
||||
Files.write(configPath, new Gson().toJson(delayList).getBytes());
|
||||
send(colored("Saved config", GREEN));
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset()
|
||||
private static void setDelayList(int[] delayList) throws IllegalArgumentException
|
||||
{
|
||||
cancel();
|
||||
attempt = 0;
|
||||
connect = false;
|
||||
System.out.println("reset");
|
||||
// if null or empty or contains negatives or zeros
|
||||
if (delayList == null || delayList.length == 0 || IntStream.of(delayList).anyMatch(i -> i <= 0))
|
||||
throw new IllegalArgumentException("delayList must be a non-empty list of strictly positive integers");
|
||||
AutoReconnect.delayList = delayList;
|
||||
}
|
||||
|
||||
public static int getCountdown()
|
||||
public static void resetAttempts()
|
||||
{
|
||||
return countdown.get();
|
||||
ticks = -1;
|
||||
attempt = -1;
|
||||
log("reset");
|
||||
}
|
||||
}
|
||||
|
||||
public static String getMessage()
|
||||
{
|
||||
return attempt < 0 ? "Could not reconnect" : String.format("Reconnect in %d...", ticks / 20 + 1);
|
||||
}
|
||||
|
||||
public static int getColor()
|
||||
{
|
||||
return Optional.of(attempt < 0 ? RED : GREEN).filter(Formatting::isColor).map(Formatting::getColorValue).orElse(0xFFFFFF);
|
||||
}
|
||||
}
|
||||
47
src/main/java/net/autoreconnect/ClientCommands.java
Normal file
@ -0,0 +1,47 @@
|
||||
package net.autoreconnect;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import net.minecraft.client.network.ClientCommandSource;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static net.autoreconnect.AutoReconnect.MOD_ID;
|
||||
|
||||
public final class ClientCommands
|
||||
{
|
||||
private ClientCommands() { }
|
||||
|
||||
private static final List<LiteralArgumentBuilder<ClientCommandSource>> commands = new ArrayList<>();
|
||||
|
||||
public static boolean contains(String command)
|
||||
{
|
||||
// checks if command starts with '<MOD_ID> ' and looks for a command literal to match the second word in the command
|
||||
return command.startsWith(MOD_ID + " ") && commands.stream().map(LiteralArgumentBuilder::getLiteral).anyMatch(command.substring(MOD_ID.length() + 1).split(" ", 2)[0]::equals);
|
||||
}
|
||||
|
||||
// register commands more or less the usual way
|
||||
public static void register(LiteralArgumentBuilder<ClientCommandSource> command)
|
||||
{
|
||||
commands.add(command);
|
||||
}
|
||||
|
||||
// variants of static methods literal and argument from net.minecraft.server.command.CommandManager replacing ServerCommandSource with ClientCommandSource
|
||||
public static LiteralArgumentBuilder<ClientCommandSource> literal(String literal)
|
||||
{
|
||||
return LiteralArgumentBuilder.literal(literal);
|
||||
}
|
||||
|
||||
public static <T> RequiredArgumentBuilder<ClientCommandSource, T> argument(String name, ArgumentType<T> type)
|
||||
{
|
||||
return RequiredArgumentBuilder.argument(name, type);
|
||||
}
|
||||
|
||||
// actually register the commands and add the prefix node
|
||||
public static void register(CommandDispatcher<ClientCommandSource> dispatcher)
|
||||
{
|
||||
commands.stream().map(literal(MOD_ID)::then).forEach(dispatcher::register);
|
||||
}
|
||||
}
|
||||
55
src/main/java/net/autoreconnect/Util.java
Normal file
@ -0,0 +1,55 @@
|
||||
package net.autoreconnect;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TextColor;
|
||||
import net.minecraft.util.Formatting;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static net.minecraft.util.Formatting.RED;
|
||||
|
||||
public class Util
|
||||
{
|
||||
// sends message to player if in game or logs to console with prefix '[<MOD_NAME>]'
|
||||
public static void send(Text text)
|
||||
{
|
||||
ClientPlayerEntity player = MinecraftClient.getInstance().player;
|
||||
if (player == null)
|
||||
{
|
||||
// if player is not in game then print to console in error or default stream depending on weather the text is red or not
|
||||
Logger logger = LogManager.getLogger("AutoReconnect");
|
||||
if (Objects.equals(text.getStyle().getColor(), TextColor.fromFormatting(RED)))
|
||||
{
|
||||
logger.error("[AutoReconnect] " + text.getString());
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("[AutoReconnect] " + text.getString());
|
||||
}
|
||||
}
|
||||
else player.sendMessage(text, false);
|
||||
}
|
||||
|
||||
// logs in console with prefix '[<MOD_NAME>]'
|
||||
public static void log(String format, Object... args)
|
||||
{
|
||||
LogManager.getLogger("AutoReconnect").info("[AutoReconnect] " + String.format(format, args));
|
||||
}
|
||||
|
||||
// easy text creation methods
|
||||
public static Text colored(String text, Formatting formatting)
|
||||
{
|
||||
return new LiteralText(text).formatted(formatting);
|
||||
}
|
||||
|
||||
public static Text err(Exception ex)
|
||||
{
|
||||
return colored(ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage(), RED);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package net.autoreconnect.mixin;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.autoreconnect.ClientCommands;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.network.ClientCommandSource;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket;
|
||||
import net.minecraft.server.command.CommandSource;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
public class MixinClientPlayNetworkHandler
|
||||
{
|
||||
@Shadow
|
||||
private CommandDispatcher<CommandSource> commandDispatcher;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void init(MinecraftClient client, Screen screen, ClientConnection connection, GameProfile profile, CallbackInfo info)
|
||||
{
|
||||
// register commands to the initial dispatcher
|
||||
ClientCommands.register((CommandDispatcher<ClientCommandSource>) (Object) commandDispatcher);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Inject(method = "onCommandTree", at = @At("TAIL"))
|
||||
private void onCommandTree(CommandTreeS2CPacket packet, CallbackInfo info)
|
||||
{
|
||||
// register commands to the new dispatcher
|
||||
ClientCommands.register((CommandDispatcher<ClientCommandSource>) (Object) commandDispatcher);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package net.autoreconnect.mixin;
|
||||
|
||||
import net.autoreconnect.ClientCommands;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientCommandSource;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import static net.autoreconnect.Util.err;
|
||||
import static net.autoreconnect.Util.send;
|
||||
|
||||
@Mixin(ClientPlayerEntity.class)
|
||||
public abstract class MixinClientPlayerEntity
|
||||
{
|
||||
@Shadow @Final
|
||||
protected MinecraftClient client;
|
||||
|
||||
@Shadow @Final
|
||||
public ClientPlayNetworkHandler networkHandler;
|
||||
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "sendChatMessage", cancellable = true)
|
||||
private void sendChatMessage(String message, CallbackInfo info)
|
||||
{
|
||||
if (message.charAt(0) == '/')
|
||||
{
|
||||
String command = message.substring(1);
|
||||
if(ClientCommands.contains(command))
|
||||
{
|
||||
// prevent message from being sent
|
||||
info.cancel();
|
||||
try
|
||||
{
|
||||
networkHandler.getCommandDispatcher().execute(command, new ClientCommandSource(networkHandler, client));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
send(err(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,32 +6,37 @@ import net.minecraft.client.gui.screen.DisconnectedScreen;
|
||||
import net.minecraft.client.util.Window;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import static net.autoreconnect.AutoReconnect.attempt;
|
||||
import static net.autoreconnect.AutoReconnect.getCountdown;
|
||||
import static net.autoreconnect.AutoReconnect.*;
|
||||
|
||||
@Mixin(DisconnectedScreen.class)
|
||||
public class MixinDisconnectedScreen
|
||||
{
|
||||
@Shadow
|
||||
private int reasonHeight;
|
||||
|
||||
// make this screen closable by pressing escape
|
||||
@Inject(at = @At("RETURN"), method = "shouldCloseOnEsc", cancellable = true)
|
||||
private void shouldCloseOnEsc(CallbackInfoReturnable<Boolean> info)
|
||||
{
|
||||
info.setReturnValue(true);
|
||||
}
|
||||
|
||||
// render the text overlay
|
||||
@Inject(at = @At("RETURN"), method = "render")
|
||||
private void render(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo info)
|
||||
{
|
||||
Window window = MinecraftClient.getInstance().getWindow();
|
||||
TextRenderer renderer = MinecraftClient.getInstance().textRenderer;
|
||||
String text = attempt == -1 ? "Can not reconnect!" : "Reconnecting in " + getCountdown() + "...";
|
||||
String text = getMessage();
|
||||
renderer.draw(matrices, text,
|
||||
(window.getScaledWidth() - renderer.getWidth(text)) / 2F,
|
||||
(window.getScaledHeight() - renderer.fontHeight) / 3F,
|
||||
0xFF4422);
|
||||
(window.getScaledWidth() - renderer.getWidth(text)) / 2F, // centered
|
||||
(window.getScaledHeight() - reasonHeight) / 2F - 9 * 4, // 9 * 2 higher than the title which is 9 * 2 higher than the disconnect reason
|
||||
getColor());
|
||||
}
|
||||
}
|
||||
@ -5,76 +5,76 @@ import net.minecraft.client.gui.screen.*;
|
||||
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
||||
import net.minecraft.client.network.ServerInfo;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import static net.autoreconnect.AutoReconnect.*;
|
||||
import static net.autoreconnect.Util.log;
|
||||
|
||||
@Mixin(MinecraftClient.class)
|
||||
public class MixinMinecraftClient
|
||||
{
|
||||
private ServerInfo lastServerEntry;
|
||||
@Shadow
|
||||
public Screen currentScreen;
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "setCurrentServerEntry")
|
||||
private void setCurrentServerEntry(ServerInfo info, CallbackInfo ci)
|
||||
private void setCurrentServerEntry(ServerInfo serverInfo, CallbackInfo info)
|
||||
{
|
||||
if (info != null)
|
||||
//save last known non-null server entry
|
||||
if (serverInfo != null)
|
||||
{
|
||||
lastServerEntry = info;
|
||||
lastServerEntry = serverInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "openScreen")
|
||||
private void openScreen(Screen screen, CallbackInfo info)
|
||||
{
|
||||
System.out.println(screen == null ? null : screen.getClass().getSimpleName());
|
||||
//TODO interpret disconnect reason
|
||||
//TODO revalidate session if needed
|
||||
if (screen instanceof DisconnectedScreen)
|
||||
{
|
||||
if (attempt < 0) return;
|
||||
switch (attempt++)
|
||||
{
|
||||
case 0:
|
||||
startCountdown(3);
|
||||
break;
|
||||
case 1:
|
||||
startCountdown(10);
|
||||
break;
|
||||
case 2:
|
||||
startCountdown(60);
|
||||
break;
|
||||
case 3:
|
||||
startCountdown(300);
|
||||
break;
|
||||
default:
|
||||
attempt = -1;
|
||||
}
|
||||
}
|
||||
else if (screen instanceof MultiplayerScreen || MinecraftClient.getInstance().player != null)
|
||||
{
|
||||
System.out.println(screen == null ? null : screen.getClass().getSimpleName());
|
||||
//TODO find better conditions to reset
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "tick")
|
||||
@Inject(at = @At("HEAD"), method = "tick")
|
||||
private void tick(CallbackInfo info)
|
||||
{
|
||||
//TODO find better way to call connect on main thread from after timer countdown finished
|
||||
if (connect)
|
||||
// if not paused, decrements countdown until its negative, succeeds if its 0
|
||||
if (ticks >= 0 && --ticks == 0)
|
||||
{
|
||||
connect = false;
|
||||
MinecraftClient mc = MinecraftClient.getInstance();
|
||||
if (lastServerEntry == null)
|
||||
{
|
||||
attempt = -1;
|
||||
return;
|
||||
resetAttempts();
|
||||
}
|
||||
else
|
||||
{
|
||||
MinecraftClient mc = MinecraftClient.getInstance();
|
||||
mc.openScreen(new ConnectScreen(new MultiplayerScreen(new TitleScreen()), mc, lastServerEntry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("INVOKE"), method = "openScreen")
|
||||
private void openScreen(Screen newScreen, CallbackInfo info)
|
||||
{
|
||||
// old and new screen must not be the same type, actually happens very often for some reason
|
||||
if ((currentScreen == null ? null : currentScreen.getClass()) != (newScreen == null ? null : newScreen.getClass()))
|
||||
{
|
||||
if (currentScreen instanceof DisconnectedScreen && ( // exited disconnect screen using...
|
||||
newScreen instanceof MultiplayerScreen || // ...cancel button on disconnect screen
|
||||
newScreen instanceof TitleScreen || // ...escape key
|
||||
newScreen != null && newScreen.getClass().getSimpleName().equals("AuthScreen")) || // ...AuthMe re-authenticate button
|
||||
(currentScreen instanceof ConnectScreen && !(newScreen instanceof DisconnectedScreen))) // connection successful or cancelled using cancel button on connect screen
|
||||
{
|
||||
resetAttempts();
|
||||
}
|
||||
// player got disconnected
|
||||
else if (newScreen instanceof DisconnectedScreen)
|
||||
{
|
||||
// if last known server is not null and next attempt is configured
|
||||
if (lastServerEntry != null && ++attempt < delayList.length)
|
||||
{
|
||||
ticks = delayList[attempt] * 20;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetAttempts();
|
||||
}
|
||||
log("lastServerEntry: %s, attempt: %d", lastServerEntry == null ? "null" : lastServerEntry.name, attempt);
|
||||
}
|
||||
mc.disconnect();
|
||||
mc.openScreen(new ConnectScreen(new MultiplayerScreen(new TitleScreen()), mc, lastServerEntry));
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/assets/countdown.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
src/main/resources/assets/failed.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
BIN
src/main/resources/assets/icon16.png
Normal file
|
After Width: | Height: | Size: 450 B |
BIN
src/main/resources/assets/icon400.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 388 KiB |
@ -6,7 +6,9 @@
|
||||
"mixins": [
|
||||
],
|
||||
"client": [
|
||||
"MixinClientPlayNetworkHandler",
|
||||
"MixinDisconnectedScreen",
|
||||
"MixinClientPlayerEntity",
|
||||
"MixinMinecraftClient"
|
||||
],
|
||||
"injectors": {
|
||||
|
||||
@ -1,22 +1,23 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "autoreconnect",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
|
||||
"name": "AutoReconnect",
|
||||
"description": "This mod allows you to afk without fear of getting disconnected. It will make 4 attempts to reconnect you to the server you were disconnected from, with a delay of 3, 10, 60 and 300 seconds.",
|
||||
"description": "This mod will automatically try to reconnect you back to a server if you got disconnected.\nBy default, it will make 4 attempts after 3, 10, 30 and 60 seconds.",
|
||||
"authors": [
|
||||
"Bstn1802"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://github.com/Bstn1802",
|
||||
"sources": "https://github.com/Bstn1802/AutoReconnect"
|
||||
"homepage": "https://www.curseforge.com/minecraft/mc-mods/autoreconnect",
|
||||
"sources": "https://github.com/Bstn1802/AutoReconnect",
|
||||
"issues": "https://github.com/Bstn1802/AutoReconnect/issues"
|
||||
},
|
||||
|
||||
"license": "CC0-1.0",
|
||||
"icon": "assets/icon.png",
|
||||
"icon": "assets/icon16.png",
|
||||
|
||||
"environment": "*",
|
||||
"environment": "client",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"net.autoreconnect.AutoReconnect"
|
||||
@ -29,9 +30,6 @@
|
||||
"depends": {
|
||||
"fabricloader": ">=0.7.4",
|
||||
"fabric": "*",
|
||||
"minecraft": "1.16.x"
|
||||
},
|
||||
"suggests": {
|
||||
"flamingo": "*"
|
||||
"minecraft": ">=1.16"
|
||||
}
|
||||
}
|
||||
|
||||