**Gemini 2.5

문제 분석**

  1. 초기 상태: TitleScreen에서 "전장으로..." 버튼을 누르면 ConnectScreen.startConnecting(this, minecraft, ...)가 호출됩니다. 이때 this는 TitleScreen의 인스턴스이므로, ConnectScreen의 parent 화면은 TitleScreen으로 올바르게 설정됩니다.
  2. 첫 번째 연결 시도 및 취소: ConnectScreen이 표시되고 연결 시도가 시작됩니다. 이때 "서버 접속하는 중" 메시지가 처음 표시됩니다. 이 상태에서 "취소" 버튼을 누르면 ConnectScreen은 자신의 parent인 TitleScreen으로 돌아가게 됩니다. (minecraft.setScreen(this.parent) 로직). 이는 예상대로 동작합니다.
  3. 두 번째 연결 시도 및 취소 (문제 발생 지점): 마인크래프트의 서버 접속 과정은 여러 단계로 이루어집니다 (주소 확인, 연결 수립, 로그인 등). ConnectScreen은 이 과정 동안 내부적으로 상태가 변경되거나, 경우에 따라서는 마인크래프트 클라이언트가 새로운 화면 인스턴스 (여전히 ConnectScreen 타입일 수 있지만 내부 상태나 parent가 다를 수 있음)를 생성하여 표시할 수 있습니다. 특히 로그인 단계 등으로 넘어가면서 내부적으로 화면 전환이 일어날 가능성이 있습니다.
  4. 원인 추정: 두 번째 "서버 접속하는 중" 메시지가 표시될 때의 ConnectScreen (또는 유사한 연결 화면)은, 최초에 TitleScreen을 parent로 가졌던 그 인스턴스가 아닐 가능성이 높습니다. 마인크래프트 내부 로직에 의해 새로 생성되거나 전환된 화면일 수 있으며, 이 화면의 기본 "취소" 동작 또는 parent가 JoinMultiplayerScreen (멀티플레이어 서버 목록 화면)으로 설정되어 있을 수 있습니다. 이는 일반적인 멀티플레이 서버 목록에서 서버에 접속하다 실패하거나 취소했을 때 다시 서버 목록으로 돌아가는 기본 동작과 일치합니다. 즉, TitleScreen에서 직접 연결을 시작했음에도 불구하고, 연결 과정 중 내부적으로 화면 스택이나 parent 정보가 기본 멀티플레이 흐름처럼 변경되었을 가능성이 큽니다.

해결 방안 제안

이 문제를 해결하기 위해 몇 가지 접근 방법을 시도해 볼 수 있습니다. 가장 확실한 방법은 ConnectScreen의 동작을 직접 제어하는 것입니다.

방법 1: 커스텀 ConnectScreen 사용

  1. ConnectScreen을 상속하는 새로운 클래스(CustomConnectScreen 등)를 만듭니다.
  2. 이 커스텀 클래스에서 "취소" 버튼의 동작(onClose() 메소드 또는 버튼의 onPress 람다)을 오버라이드(Override)하거나 수정합니다. 취소 시 무조건 TitleScreen으로 돌아가도록 명시적으로 minecraft.setScreen(new TitleScreen()); 또는 미리 저장해둔 TitleScreen 인스턴스를 사용하도록 만듭니다.
  3. TitleScreenMixin에서 ConnectScreen.startConnecting 대신, 직접 만든 CustomConnectScreen의 인스턴스를 생성하고 minecraft.setScreen()을 호출하여 연결을 시작합니다.
// 예시: CustomConnectScreen (실제 구현 시 필요한 생성자 등을 맞춰야 합니다)
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.multiplayer.resolver.ServerAddress;
import net.minecraft.client.server.LanServerDetection; // 필요한 import 추가
import net.minecraft.network.chat.Component; // 필요한 import 추가
import net.minecraft.client.multiplayer.ServerData; // 필요한 import 추가
import net.minecraft.client.Minecraft; // 필요한 import 추가
import java.util.concurrent.atomic.AtomicReference; // 필요한 import 추가

public class CustomConnectScreen extends ConnectScreen {

    private final Screen returnScreen; // 돌아갈 화면을 명시적으로 저장

    // 생성자는 ConnectScreen의 생성자를 참고하여 필요한 인자를 받도록 수정해야 합니다.
    // 가장 간단한 방법은 startConnecting과 유사한 정적 메소드를 만드는 것입니다.
    public CustomConnectScreen(Screen parent, Minecraft mc, ServerAddress address, ServerData serverData, boolean isQuickPlay) {
        // super(...) 호출은 ConnectScreen의 생성자에 맞춰야 합니다.
        // Forge 47.2.20 / MC 1.20.1 ConnectScreen 생성자는 약간 다를 수 있으니 확인 필요
        // 예시: super(parent, mc, address, serverData, isQuickPlay); -> 실제 생성자와 다를 수 있음!
        // 정확한 생성자 시그니처를 확인하고 호출해야 합니다.
        // ConnectScreen 생성자가 protected일 수 있으므로, startConnecting 같은 팩토리 메소드 패턴을 사용해야 할 수 있습니다.

        super(parent, Component.translatable("connect.connecting"), new AtomicReference<>()); // 임시 생성자 호출, 실제로는 startConnecting 내부 로직 참고 필요
        this.returnScreen = parent; // TitleScreen을 명시적으로 저장
        // ... ConnectScreen의 초기화 로직 (connect 메소드 호출 등) ...
        // connect(mc, address, serverData, isQuickPlay); // connect 메소드 호출 방식 확인 필요
    }

    // startConnecting과 유사한 정적 팩토리 메소드
    public static void startConnecting(Screen parent, Minecraft mc, ServerAddress address, ServerData serverData, boolean isQuickPlay) {
        // ConnectScreen.startConnecting 내부 로직을 참고하여 CustomConnectScreen 인스턴스를 생성하고 설정
        Screen connectScreen = new CustomConnectScreen(parent, mc, address, serverData, isQuickPlay); // 실제 생성자 호출 방식으로 수정 필요
        mc.setScreen(connectScreen);
        // ConnectScreen.startConnecting 내부의 connect 호출 로직 실행 필요
        // ((CustomConnectScreen)connectScreen).connect(mc, address, serverData, isQuickPlay); // 예시
    }

    // 취소 시 동작 재정의 (정확한 메소드 이름은 ConnectScreen 내부 확인 필요, onClose 또는 버튼 액션)
    @Override
    public void onClose() {
        // 연결 취소 관련 로직 (예: connection?.disconnect())
        super.onClose(); // 원래 로직을 호출해야 할 수도 있음
        this.minecraft.setScreen(this.returnScreen); // 항상 저장된 returnScreen(TitleScreen)으로 돌아가도록 설정
    }

    // 또는 Cancel 버튼의 onPress 액션을 직접 수정해야 할 수도 있습니다.
    // init() 메소드 등에서 버튼 생성 시 람다를 수정하는 방식
}

// TitleScreenMixin 수정
@Mixin(TitleScreen.class)
public abstract class TitleScreenMixin extends Screen {
    @Inject(method = "createNormalMenuOptions", at = @At("HEAD"), cancellable = true)
    public void createNormalMenuOptions(int p_96764_, int p_96765_, CallbackInfo callbackInfo) {
        // ... (기존 코드) ...

        addRenderableWidget(Button.builder(Component.literal("전장으로..."), (p_210872_) -> {
            var serverData = new ServerData("", "서버주소", ServerData.Type.OTHER); // ServerData 생성 방식 변경 확인 (1.20.1)
            // ConnectScreen.startConnecting(this, minecraft, ServerAddress.parseString(serverData.ip), serverData, false); // 기존 코드
            CustomConnectScreen.startConnecting(this, minecraft, ServerAddress.parseString(serverData.ip), serverData, false); // 커스텀 스크린 사용
        }).bounds(this.width / 2 - 100, p_96764_, 200, 20).tooltip(tooltip).build()).active = flag;

        // ... (나머지 코드) ...

        callbackInfo.cancel();
    }
}

주의: 위 CustomConnectScreen 코드는 개념적인 예시입니다. 마인크래프트/포지 버전에 따라 ConnectScreen의 정확한 생성자, connect 메소드 호출 방식, 취소 로직(버튼 콜백, onClose 등)이 다를 수 있습니다. 실제 ConnectScreen 코드를 디컴파일하거나 소스 코드를 참조하여 정확하게 구현해야 합니다. 특히 ConnectScreen 생성 및 connect 메소드 호출 부분을 ConnectScreen.startConnecting 정적 메소드의 내부 구현을 보고 맞춰주어야 합니다.

방법 2: Mixin으로 Minecraft.setScreen 가로채기 (더 복잡하고 위험)

Minecraft.setScreen(Screen) 메소드 자체에 Mixin을 적용하여, ConnectScreen으로 화면이 설정될 때 parent가 TitleScreen이 아니면 강제로 TitleScreen으로 설정하는 방법도 생각해볼 수 있지만, 이는 다른 모드와의 충돌이나 예상치 못한 부작용을 일으킬 가능성이 커서 권장하지 않습니다.

결론