Skip to content

Commit

Permalink
Fix gui positioning and world list return, add method for other mods …
Browse files Browse the repository at this point in the history
…to cancel reconnect
  • Loading branch information
NotRyken committed Aug 2, 2024
1 parent fa2f508 commit cef305d
Show file tree
Hide file tree
Showing 19 changed files with 521 additions and 330 deletions.
4 changes: 3 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
- Add an error indicator for condition patterns
- Adjust screens to fix misalignment issues
- Fix returning to server list from singleplayer disconnect screen
- Add a method for other mods to pre-emptively cancel reconnection
190 changes: 70 additions & 120 deletions common/src/main/java/dev/terminalmc/autoreconnectrf/AutoReconnect.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,24 @@

package dev.terminalmc.autoreconnectrf;

import com.mojang.logging.LogUtils;
import com.mojang.realmsclient.RealmsMainScreen;
import dev.terminalmc.autoreconnectrf.config.Config;
import dev.terminalmc.autoreconnectrf.reconnect.ReconnectStrategy;
import dev.terminalmc.autoreconnectrf.reconnect.SingleplayerReconnectStrategy;
import dev.terminalmc.autoreconnectrf.util.ModLogger;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.DisconnectedScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.chat.contents.TranslatableContents;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntConsumer;
Expand All @@ -45,71 +41,20 @@ public class AutoReconnect {
public static final String MOD_ID = "autoreconnectrf";
public static final String MOD_NAME = "AutoReconnect-Reforged";
public static final ModLogger LOG = new ModLogger(MOD_NAME);
public static final List<String> DISCONNECT_KEYS = List.of(
"disconnect.closed",
"disconnect.disconnected",
"disconnect.endOfStream",
"disconnect.exceeded_packet_rate",
"disconnect.genericReason", // arg
"disconnect.ignoring_status_request",
"disconnect.loginFailed",
"disconnect.loginFailedInfo", // arg
"disconnect.loginFailedInfo.insufficientPrivileges",
"disconnect.loginFailedInfo.invalidSession",
"disconnect.loginFailedInfo.serversUnavailable",
"disconnect.loginFailedInfo.userBanned",
"disconnect.lost",
"disconnect.overflow",
"disconnect.packetError",
"disconnect.spam",
"disconnect.timeout",
"disconnect.transfer",
"disconnect.unknownHost",

"multiplayer.disconnect.authservers_down",
"multiplayer.disconnect.banned",
"multiplayer.disconnect.banned_ip.reason", // arg
"multiplayer.disconnect.banned.reason", // arg
"multiplayer.disconnect.chat_validation_failed",
"multiplayer.disconnect.duplicate_login",
"multiplayer.disconnect.expired_public_key",
"multiplayer.disconnect.flying",
"multiplayer.disconnect.generic",
"multiplayer.disconnect.idling",
"multiplayer.disconnect.illegal_characters",
"multiplayer.disconnect.incompatible", // arg
"multiplayer.disconnect.invalid_entity_attacked",
"multiplayer.disconnect.invalid_packet",
"multiplayer.disconnect.invalid_player_data",
"multiplayer.disconnect.invalid_player_movement",
"multiplayer.disconnect.invalid_public_key_signature",
"multiplayer.disconnect.invalid_public_key_signature",
"multiplayer.disconnect.invalid_vehicle_movement",
"multiplayer.disconnect.ip_banned",
"multiplayer.disconnect.kicked",
"multiplayer.disconnect.missing_tags",
"multiplayer.disconnect.name_taken",
"multiplayer.disconnect.not_whitelisted",
"multiplayer.disconnect.out_of_order_chat",
"multiplayer.disconnect.outdated_client", // arg
"multiplayer.disconnect.outdated_server", // arg
"multiplayer.disconnect.server_full",
"multiplayer.disconnect.server_shutdown",
"multiplayer.disconnect.slow_login",
"multiplayer.disconnect.too_many_pending_chats",
"multiplayer.disconnect.transfers_disabled",
"multiplayer.disconnect.unexpected_query_response",
"multiplayer.disconnect.unsigned_chat",
"multiplayer.disconnect.unverified_username",

"multiplayer.requiredTexturePrompt.disconnect"
);

// Condition vars
public static final List<Pattern> conditionPatterns = new ArrayList<>();

public static @Nullable String lastDcReasonStr = null;
public static @Nullable String lastDcReasonKey = null;

// Reconnect vars
private static final ScheduledThreadPoolExecutor EXECUTOR_SERVICE = new ScheduledThreadPoolExecutor(1);
static { EXECUTOR_SERVICE.setRemoveOnCancelPolicy(true); }
private static final AtomicReference<ScheduledFuture<?>> countdown = new AtomicReference<>(null);
private static @Nullable ReconnectStrategy reconnectStrategy = null;

// Mod lifecycle methods

public static void init() {
Config.getAndSave();
}
Expand All @@ -126,67 +71,85 @@ public static void onConfigSaved(Config config) {
}
}

// Legacy
// Reconnect methods

private static final ScheduledThreadPoolExecutor EXECUTOR_SERVICE = new ScheduledThreadPoolExecutor(1);
private static final AtomicReference<ScheduledFuture<?>> countdown = new AtomicReference<>(null);
private static ReconnectStrategy reconnectStrategy = null;

static {
EXECUTOR_SERVICE.setRemoveOnCancelPolicy(true);
/**
* Stops any active reconnection, and removes the saved strategy to prevent
* future reconnection.
*
* <p>Any mods wanting to prevent automatic reconnection should invoke this
* method at any time after the player has joined a world/server/realm.</p>
*/
public static void cancelAutoReconnect() {
cancelActiveReconnect();
reconnectStrategy = null;
}

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

public static void setReconnectHandler(ReconnectStrategy pReconnectStrategy) {
if (reconnectStrategy != null) {
// should imply that both handlers target the same world/server
// we return to preserve the attempts counter
assert reconnectStrategy.getClass().equals(pReconnectStrategy.getClass()) &&
reconnectStrategy.getName().equals(pReconnectStrategy.getName());
return;
/**
* Sets the strategy to be used for the next reconnection attempt.
*/
public static void setReconnectStrategy(@NotNull ReconnectStrategy pReconnectStrategy) {
// Avoid overwriting strategy on reconnect failure
if (reconnectStrategy == null) {
reconnectStrategy = pReconnectStrategy;
}
reconnectStrategy = pReconnectStrategy;
}

/**
* @return {@code true} if the mod has a reconnection strategy,
* {@code false} otherwise.
*/
public static boolean canReconnect() {
return reconnectStrategy != null;
}

/**
* Attempts to reconnect using the stored strategy.
*/
public static void reconnect() {
if (reconnectStrategy == null) return; // shouldn't happen normally, but can be forced
cancelCountdown();
reconnectStrategy.reconnect();
checkStrategy().reconnect();
}

/**
* Initiates the countdown for the next reconnect attempt, if any.
*/
public static 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 = Config.get().getDelayForAttempt(reconnectStrategy.nextAttempt());
int delay = Config.get().getDelayForAttempt(checkStrategy().nextAttempt());
if (delay >= 0) {
countdown(delay, callback);
} else {
// no more attempts configured
// No more attempts configured
callback.accept(-1);
}
}

public static void cancelAutoReconnect() {
if (reconnectStrategy == null) return; // should not happen
reconnectStrategy.resetAttempts();
/**
* Stops attempting reconnection but retains strategy for manual reconnect.
*/
public static void cancelActiveReconnect() {
if (reconnectStrategy != null) reconnectStrategy.resetAttempts();
cancelCountdown();
}

/**
* Resets the reconnect countdown and attempts to reconnect using the saved
* strategy.
*/
public static void manualReconnect() {
AutoReconnect.cancelActiveReconnect();
AutoReconnect.reconnect();
}

public static void onScreenChanged(Screen current, Screen next) {
if (sameType(current, next)) return;
// TODO condition could use some improvement, shouldn't cause any issues tho
// TODO condition could use some improvement, shouldn't cause any issues
if (!isMainScreen(current) && isMainScreen(next) || isReAuthenticating(current, next)) {
cancelAutoReconnect();
reconnectStrategy = null;
}
}

Expand Down Expand Up @@ -258,10 +221,7 @@ private static void sendAutomatedMessages(LocalPlayer player, Iterator<String> m
*/
private static void sendMessage(LocalPlayer player, String message) {
if (message.startsWith("/")) {
// The first starting slash has to be removed,
// otherwise it will be interpreted as a double slash.
String command = message.substring(1);
player.connection.sendUnsignedCommand(command);
player.connection.sendUnsignedCommand(message.substring(1));
} else {
player.connection.sendChat(message);
}
Expand All @@ -278,27 +238,17 @@ private static boolean isMainScreen(Screen screen) {
screen instanceof JoinMultiplayerScreen || 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");
private static boolean isReAuthenticating(Screen current, Screen next) {
return current instanceof DisconnectedScreen
&& next != null
&& next.getClass().getName().startsWith("me.axieum.mcmod.authme");
}

public static Optional<Button> findBackButton(Screen screen) {
for (GuiEventListener element : screen.children()) {
if (!(element instanceof Button button)) continue;

String translatableKey;
if (button.getMessage() instanceof TranslatableContents translatable) {
translatableKey = translatable.getKey();
} else if (button.getMessage().getContents() instanceof TranslatableContents 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);
}
private static @NotNull ReconnectStrategy checkStrategy() {
if (reconnectStrategy == null) {
throw new IllegalStateException("Reconnect strategy failed null check");
} else {
return reconnectStrategy;
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -116,7 +117,7 @@ static Screen getConfigScreen(Screen parent) {
() -> options.conditionKeys,
val -> options.conditionKeys = val)
.controller(option -> DropdownStringControllerBuilder.create(option)
.values(AutoReconnect.DISCONNECT_KEYS)
.values(DISCONNECT_KEYS)
.allowAnyValue(true)
.allowEmptyValue(false))
.initial("")
Expand Down Expand Up @@ -238,7 +239,7 @@ public static void reload(YACLScreen screen) {
}

// Various shenanigans to implement a custom string option validator, of sorts
// If you're overly concerned about code quality, turn back now
// If you're overly concerned about code quality, look away

public interface IRestrictedStringControllerBuilder extends ControllerBuilder<String> {
static IRestrictedStringControllerBuilder create(Option<String> option) {
Expand Down Expand Up @@ -335,4 +336,64 @@ public void render(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float
super.isFocused(), super.getRectangle());
}
}

public static final List<String> DISCONNECT_KEYS = List.of(
"disconnect.closed",
"disconnect.disconnected",
"disconnect.endOfStream",
"disconnect.exceeded_packet_rate",
"disconnect.genericReason", // arg
"disconnect.ignoring_status_request",
"disconnect.loginFailed",
"disconnect.loginFailedInfo", // arg
"disconnect.loginFailedInfo.insufficientPrivileges",
"disconnect.loginFailedInfo.invalidSession",
"disconnect.loginFailedInfo.serversUnavailable",
"disconnect.loginFailedInfo.userBanned",
"disconnect.lost",
"disconnect.overflow",
"disconnect.packetError",
"disconnect.spam",
"disconnect.timeout",
"disconnect.transfer",
"disconnect.unknownHost",

"multiplayer.disconnect.authservers_down",
"multiplayer.disconnect.banned",
"multiplayer.disconnect.banned_ip.reason", // arg
"multiplayer.disconnect.banned.reason", // arg
"multiplayer.disconnect.chat_validation_failed",
"multiplayer.disconnect.duplicate_login",
"multiplayer.disconnect.expired_public_key",
"multiplayer.disconnect.flying",
"multiplayer.disconnect.generic",
"multiplayer.disconnect.idling",
"multiplayer.disconnect.illegal_characters",
"multiplayer.disconnect.incompatible", // arg
"multiplayer.disconnect.invalid_entity_attacked",
"multiplayer.disconnect.invalid_packet",
"multiplayer.disconnect.invalid_player_data",
"multiplayer.disconnect.invalid_player_movement",
"multiplayer.disconnect.invalid_public_key_signature",
"multiplayer.disconnect.invalid_public_key_signature",
"multiplayer.disconnect.invalid_vehicle_movement",
"multiplayer.disconnect.ip_banned",
"multiplayer.disconnect.kicked",
"multiplayer.disconnect.missing_tags",
"multiplayer.disconnect.name_taken",
"multiplayer.disconnect.not_whitelisted",
"multiplayer.disconnect.out_of_order_chat",
"multiplayer.disconnect.outdated_client", // arg
"multiplayer.disconnect.outdated_server", // arg
"multiplayer.disconnect.server_full",
"multiplayer.disconnect.server_shutdown",
"multiplayer.disconnect.slow_login",
"multiplayer.disconnect.too_many_pending_chats",
"multiplayer.disconnect.transfers_disabled",
"multiplayer.disconnect.unexpected_query_response",
"multiplayer.disconnect.unsigned_chat",
"multiplayer.disconnect.unverified_username",

"multiplayer.requiredTexturePrompt.disconnect"
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@

@Mixin(ClientPacketListener.class)
public class MixinClientPacketListener {
@Inject(at = @At("TAIL"), method = "handleLogin")
@Inject(
at = @At("TAIL"),
method = "handleLogin"
)
private void onGameJoin(ClientboundLoginPacket packet, CallbackInfo info) {
AutoReconnect.onGameJoined();
}
Expand Down
Loading

0 comments on commit cef305d

Please sign in to comment.