Skip to content

Commit

Permalink
Update for 1.20 (minor changes after merging PR)
Browse files Browse the repository at this point in the history
Updated the readme
Bumped version of this mod and dependencies
Changed way of logging
Changed text of disconnect screen after being disconnected from a singleplayer world to "Back to World List" using translation key "gui.toWorld" instead of "Back" (gui.back)
Improved method of finding the back button on a screen now looking for all the possible back buttons (back to menu/title/realms/world) also returning an optional instead of throwing
Quick fix for reconnect strategy being null when using quick play/mods/multimc to directly connect to a world/server/realm, ignoring the error for now
Refactored mixins: code that used to be injected into disconnect screen constructor now injected into init and now using Unique annotation where appropriate
  • Loading branch information
Bstn1802 committed Jul 25, 2023
1 parent 1ec7e61 commit faa2189
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 98 deletions.
88 changes: 52 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,54 @@
# AutoReconnect [1.18.x][Fabric][Client]
# AutoReconnect [1.20.x][Fabric][Client]

### 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.

_Disclaimer:_ Use at your own risk. When using this on a multiplayer server/realm you might want to check with the admins first whether it's okay to use this mod.

### Features

* Additional button on the Disconnect Screen which will reconnect you without having to go back to the menu first
* AutoReconnect
* Automatically reconnect after getting disconnected
General:
* Additional button on the disconnect screen which will reconnect you without having to go back to the menu first
* Configurable through [Mod Menu](https://www.curseforge.com/minecraft/mc-mods/modmenu)

AutoReconnect:
* Automatically reconnect after getting disconnected
* Multiple attempts
* Individual delays
* Individual delays between attempts
* Infinite attempts (Optional, repeats last attempt)
* Manual reconnect still possible
* Countdown is showing and can be canceled
* AutoMessage
* Automatically send messages after reconnecting, e.g., to join a certain lobby or just say hi to your friends ;P
* Delay between messages and before the first one
* Targets specific server, realm or singleplayer world identified by its name (for details see below)
* Only executed after automatic reconnects and not if you reconnect manually or if it hasn't even been a reconnect
* Works for Multiplayer, Realms and even for Singleplayer!
* Extras
* In-game configuration through [Mod Menu](https://www.curseforge.com/minecraft/mc-mods/modmenu)
* Support [Mod Menu](https://www.curseforge.com/minecraft/mc-mods/modmenu) and [AuthMe](https://www.curseforge.com/minecraft/mc-mods/auth-me) (for details see below)
* Disconnect Screen (like many other screens) can be exited by pressing escape
* After being disconnected from a singleplayer world, you won't end up on the Multiplayer Screen ([Bug MC-46502](https://bugs.mojang.com/browse/MC-45602))

### Installation

1. Download and install [Fabric](https://fabricmc.net/use/) and set up the profile the way you want
2. Download the following mods and put them in the mod folder:
* Manual reconnect still possible
* Countdown is showing and can be canceled
* Works for servers, realms and even singleplayer

AutoMessage:
* Automatically send messages/commands after reconnecting, e.g. to join a certain lobby or just say hi\
(Doesn't trigger when (re)connecting manually)
* Delay between messages and before the first one
* Configure messages for each server, realm or singleplayer world (for details see below)

Extras:
* Supports [Mod Menu](https://www.curseforge.com/minecraft/mc-mods/modmenu) and [AuthMe](https://www.curseforge.com/minecraft/mc-mods/auth-me) (for details see below)
* The disconnect screen (like many other screens) can be exited by pressing escape
* After being disconnected from a singleplayer world, you won't end up on the multiplayer screen. A fix has been implemented for ([Bug MC-46502](https://bugs.mojang.com/browse/MC-45602))

### Requirements/Installation/Setup

This mod works on [Fabric](https://fabricmc.net/use/) and requires the [Fabric API](https://www.curseforge.com/minecraft/mc-mods/fabric-api), [ClothConfig](https://www.curseforge.com/minecraft/mc-mods/cloth-config) and [Mod Menu](https://www.curseforge.com/minecraft/mc-mods/modmenu)

1. If you don't have a fabric profile set up, download and install [Fabric](https://fabricmc.net/use/) and set up a profile to your likings
2. Download the following mods and put them in the mod folder of your profile:\
(Be careful with the versions you download, make sure the mods are for fabric (not forge) and the right version of minecraft)
* [Fabric API](https://www.curseforge.com/minecraft/mc-mods/fabric-api)
* [ClothConfig](https://www.curseforge.com/minecraft/mc-mods/cloth-config)
* [AutoReconnect](https://www.curseforge.com/minecraft/mc-mods/autoreconnect)
* [Mod Menu](https://www.curseforge.com/minecraft/mc-mods/modmenu) (Optional but highly recommended)
* [Mod Menu](https://www.curseforge.com/minecraft/mc-mods/modmenu)
* AutoReconnect (this mod)
3. Setup:
* Start up minecraft and when you see the title screen, click on "Mods"
* Find and select this mod in the list of mods to your left
* Click on the configuration button on top of the icon of this mod in the list on the left or the one at the top right if you selected the mod
* The configuration of this mod should open and look similar to the screenshots below

### Compatibility/Support

Expand All @@ -47,20 +61,22 @@ By default, it will make 4 attempts after 3, 10, 30 and 60 seconds.

### Details

* Automatic reconnects
* Attempts can be configured by adding strictly positive values (delay in seconds) to the list of delays. For each value in that list a delayed attempt will be made to reconnect you.
* Can be disabled by simple not configuring any attempts. Only manual reconnects are possible then.
* Automatic messages
* Will only be executed if an automatic reconnect attempt has been made, so you didn't click on the reconnect button yourself.
* Targets a specific server, realm or singleplayer world. Enter the name of the server, realm or singleplayer in the configuration.
* A delay can be configured (in milliseconds). This delay will be the same between every message and even the first one and the moment you joined.
AutoReconnect:
* Attempts can be configured by adding strictly positive values (delay in seconds) to the list of delays. For each value in that list a delayed attempt will be made to reconnect you.
* Can be disabled by simple not configuring any attempts. Only manual reconnects are possible then.
* The "infinite" flag can be enabled to configure the last attempt to repeat infinitely
* The countdown can be cancelled by pressing the button with the "✕" or by pressing escape

AutoMessages:
* Will only be executed if an automatic reconnect attempt has been made, so you didn't click on the reconnect button yourself or connect the first time.
* Can target multiple specific servers, realms or singleplayer worlds. Create a configuration under the section "AutoMessages" for each one and enter the name of the server, realm or singleplayer world.
* A delay (in milliseconds) can be configured. This delay will be the same between every message and before the first message after the instant you join the world.

### Future plans

* Allow multiple AutoMessage targets so that different messages will be sent when reconnecting to different servers, realms or singleplayer worlds
* Custom configuration screen and potentially drop the cloth-config dependency
* Improve the user experience
* Allow more complex config to make the first point possible to configure
* Modify
* Reconnect from being kicked into a lobby (feature request [issue #22](https://github.com/Bstn1802/AutoReconnect/issues/22))
* Conditional reconnects, e.g. configure the mod not to reconnect when a moderator kicked you (feature request [issue #36](https://github.com/Bstn1802/AutoReconnect/issues/36))
* I'm _not_ planning to port this mod to forge

### Feedback, Suggestions, Bugs & Issues
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ loader_version=0.14.21
# Mod Properties
mod_id = autoreconnect
mod_name = AutoReconnect
mod_version = 2.1.0-beta
mod_version = 2.2.0
archives_base_name = autoreconnect

# Dependencies
fabric_version=0.83.0+1.20
modmenu_version=5.0.0
cloth_config_version=9.0.94
modmenu_version=7.0.0
cloth_config_version=11.0.99
57 changes: 35 additions & 22 deletions src/main/java/autoreconnect/AutoReconnect.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import autoreconnect.config.AutoReconnectConfig;
import autoreconnect.reconnect.ReconnectStrategy;
import autoreconnect.reconnect.SingleplayerReconnectStrategy;
import com.mojang.logging.LogUtils;
import net.fabricmc.api.ClientModInitializer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;
Expand All @@ -17,6 +18,7 @@
import net.minecraft.text.TranslatableTextContent;

import java.util.Iterator;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntConsumer;
Expand Down Expand Up @@ -45,16 +47,16 @@ public static AutoReconnectConfig getConfig() {
return AutoReconnectConfig.getInstance();
}

public static void schedule(Runnable command, long delay, TimeUnit timeUnit) {
EXECUTOR_SERVICE.schedule(command, delay, timeUnit);
public static ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit timeUnit) {
return EXECUTOR_SERVICE.schedule(command, delay, timeUnit);
}

public void setReconnectHandler(ReconnectStrategy reconnectStrategy) {
if (this.reconnectStrategy != null) {
// should imply that both handlers target the same world/server
// we return to preserve the attempts counter
assert this.reconnectStrategy.getClass().equals(reconnectStrategy.getClass()) &&
this.reconnectStrategy.getName().equals(reconnectStrategy.getName());
this.reconnectStrategy.getName().equals(reconnectStrategy.getName());
return;
}
this.reconnectStrategy = reconnectStrategy;
Expand All @@ -68,6 +70,13 @@ public void reconnect() {

public void startCountdown(final IntConsumer callback) {
// if (countdown.get() != null) return; // should not happen
if (reconnectStrategy == null) {
// TODO fix issue appropriately, logging error for now
LogUtils.getLogger().error("Cannot reconnect because reconnectStrategy is null");
callback.accept(-1); // signal reconnecting is not possible
return;
}

int delay = getConfig().getDelayForAttempt(reconnectStrategy.nextAttempt());
if (delay >= 0) {
countdown(delay, callback);
Expand Down Expand Up @@ -100,11 +109,11 @@ public void onGameJoined() {

// Send automatic messages if configured for the current context
getConfig().getAutoMessagesForName(reconnectStrategy.getName()).ifPresent(
autoMessages -> sendAutomatedMessages(
MinecraftClient.getInstance().player,
autoMessages.getMessages(),
autoMessages.getDelay()
)
autoMessages -> sendAutomatedMessages(
MinecraftClient.getInstance().player,
autoMessages.getMessages(),
autoMessages.getDelay()
)
);
}

Expand All @@ -129,8 +138,7 @@ private void countdown(int seconds, final IntConsumer callback) {
callback.accept(seconds);
// wait at end of method for no initial delay
synchronized (countdown) { // just to be sure
countdown.set(EXECUTOR_SERVICE.schedule(() ->
countdown(seconds - 1, callback), 1, TimeUnit.SECONDS));
countdown.set(schedule(() -> countdown(seconds - 1, callback), 1, TimeUnit.SECONDS));
}
}

Expand Down Expand Up @@ -178,25 +186,30 @@ private static boolean sameType(Object a, Object b) {

private static boolean isMainScreen(Screen screen) {
return screen instanceof TitleScreen || screen instanceof SelectWorldScreen ||
screen instanceof MultiplayerScreen || screen instanceof RealmsMainScreen;
screen instanceof MultiplayerScreen || screen instanceof RealmsMainScreen;
}

private static boolean isReAuthenticating(Screen from, Screen to) {
return from instanceof DisconnectedScreen && to != null &&
to.getClass().getName().startsWith("me.axieum.mcmod.authme");
to.getClass().getName().startsWith("me.axieum.mcmod.authme");
}

public static ButtonWidget findBackButton(Screen screen) {
for(Element element : screen.children()) {
if(!(element instanceof ButtonWidget button)) continue;
TranslatableTextContent translatable;
if(button.getMessage() instanceof TranslatableTextContent t) translatable = t;
else if(button.getMessage().getContent() instanceof TranslatableTextContent t) translatable = t;
else continue;
public static Optional<ButtonWidget> findBackButton(Screen screen) {
for (Element element : screen.children()) {
if (!(element instanceof ButtonWidget button)) continue;

if(translatable.getKey().equals("gui.toMenu") || translatable.getKey().equals("gui.back"))
return button;
String translatableKey;
if (button.getMessage() instanceof TranslatableTextContent translatable) {
translatableKey = translatable.getKey();
} else if (button.getMessage().getContent() instanceof TranslatableTextContent translatable) {
translatableKey = translatable.getKey();
} else continue;

// check for gui.back, gui.toMenu, gui.toRealms, gui.toTitle, gui.toWorld (only ones starting with "gui.to")
if (translatableKey.equals("gui.back") || translatableKey.startsWith("gui.to")) {
return Optional.of(button);
}
}
throw new RuntimeException("AutoReconnect: Failed to find back button!");
return Optional.empty();
}
}
6 changes: 3 additions & 3 deletions src/main/java/autoreconnect/config/AutoReconnectConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.mojang.logging.LogUtils;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.LogManager;

import java.io.IOException;
import java.nio.file.Files;
Expand Down Expand Up @@ -40,7 +40,7 @@ public static void load() {
AutoReconnectConfig.class);
instance.validate();
} catch (IOException | JsonSyntaxException ex) {
LogManager.getRootLogger().warn("AutoReconnect could not load the config", ex);
LogUtils.getLogger().warn("AutoReconnect could not load the config", ex);
instance = new AutoReconnectConfig();
}
}
Expand All @@ -62,7 +62,7 @@ public void save() {
try {
Files.writeString(FabricLoader.getInstance().getConfigDir().resolve(FILE_NAME), GSON.toJson(this));
} catch (IOException ex) {
LogManager.getRootLogger().warn("AutoReconnect could not load the config", ex);
LogUtils.getLogger().error("AutoReconnect could not save the config", ex);
}
}

Expand Down
18 changes: 8 additions & 10 deletions src/main/java/autoreconnect/mixin/DisconnectedScreenMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.screen.ScreenTexts;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
Expand All @@ -16,28 +14,28 @@

@Mixin(DisconnectedScreen.class)
public class DisconnectedScreenMixin extends Screen {
@Shadow
@Final
@Mutable
@Shadow
@Final
@Mutable
private Screen parent;

protected DisconnectedScreenMixin(Text title) {
super(title);
}

@Inject(at = @At("RETURN"), method = "<init>*")
private void constructor(Screen parent, Text title, Text reason, CallbackInfo info) {
@Inject(at = @At("RETURN"), method = "<init>(Lnet/minecraft/client/gui/screen/Screen;Lnet/minecraft/text/Text;Lnet/minecraft/text/Text;Lnet/minecraft/text/Text;)V")
private void constructor(Screen parent, Text title, Text reason, Text buttonLabel, CallbackInfo info) {
if (AutoReconnect.getInstance().isPlayingSingleplayer()) {
// make back button redirect to SelectWorldScreen instead of MultiPlayerScreen (Bug#45602)
// make back button redirect to SelectWorldScreen instead of MultiPlayerScreen (https://bugs.mojang.com/browse/MC-45602)
this.parent = new SelectWorldScreen(new TitleScreen());
}
}

@Inject(at = @At("RETURN"), method = "init")
private void init(CallbackInfo info) {
if (AutoReconnect.getInstance().isPlayingSingleplayer()) {
// change back button text to "Back" instead of "Back to Server List" bcs of bug fix above
AutoReconnect.findBackButton(this).setMessage(ScreenTexts.BACK);
// change back button text to "Back" instead of "Back to World List" bcs of bug fix above
AutoReconnect.findBackButton(this).ifPresent(btn -> btn.setMessage(Text.translatable("gui.toWorld")));
}
}

Expand Down
Loading

0 comments on commit faa2189

Please sign in to comment.