Add Bedrock Realms screen

Move out connection code into combined ConnectUtil
Refactor around AccountsSave to migrate bedrock accounts
Extend VFPListEntry and VFPScreen

Closes https://github.com/ViaVersion/ViaFabricPlus/issues/554
This commit is contained in:
FlorianMichael 2024-10-11 15:20:24 +02:00
parent 2c45810f4f
commit 9ed44d681f
No known key found for this signature in database
GPG Key ID: C2FB87E71C425126
11 changed files with 448 additions and 44 deletions

View File

@ -23,8 +23,11 @@ import com.google.gson.JsonObject;
import de.florianmichael.classic4j.model.classicube.account.CCAccount; import de.florianmichael.classic4j.model.classicube.account.CCAccount;
import de.florianmichael.viafabricplus.ViaFabricPlus; import de.florianmichael.viafabricplus.ViaFabricPlus;
import de.florianmichael.viafabricplus.save.AbstractSave; import de.florianmichael.viafabricplus.save.AbstractSave;
import de.florianmichael.viafabricplus.settings.impl.BedrockSettings;
import net.raphimc.minecraftauth.MinecraftAuth; import net.raphimc.minecraftauth.MinecraftAuth;
import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession; import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession;
import net.raphimc.minecraftauth.step.msa.StepMsaToken;
import net.raphimc.minecraftauth.step.xbl.session.StepInitialXblSession;
public class AccountsSave extends AbstractSave { public class AccountsSave extends AbstractSave {
@ -38,7 +41,7 @@ public class AccountsSave extends AbstractSave {
@Override @Override
public void write(JsonObject object) { public void write(JsonObject object) {
if (bedrockAccount != null) { if (bedrockAccount != null) {
object.add("bedrock", MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.toJson(bedrockAccount)); object.add("bedrockV2", BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.toJson(bedrockAccount));
} }
if (classicubeAccount != null) { if (classicubeAccount != null) {
object.add("classicube", classicubeAccount.asJson()); object.add("classicube", classicubeAccount.asJson());
@ -47,31 +50,37 @@ public class AccountsSave extends AbstractSave {
@Override @Override
public void read(JsonObject object) { public void read(JsonObject object) {
if (object.has("bedrock")) { handleAccount("bedrock", object, account -> {
try { // Use old login flow, then get refresh token and login via new flow
bedrockAccount = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.fromJson(object.get("bedrock").getAsJsonObject()); final StepFullBedrockSession.FullBedrockSession oldSession = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.fromJson(account);
} catch (Exception e) { final StepInitialXblSession.InitialXblSession xblSession = oldSession.getMcChain().getXblXsts().getInitialXblSession();
ViaFabricPlus.global().getLogger().error("Failed to read bedrock account!", e);
final StepMsaToken.RefreshToken refreshToken = new StepMsaToken.RefreshToken(xblSession.getMsaToken().getRefreshToken());
bedrockAccount = BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.getFromInput(MinecraftAuth.createHttpClient(), refreshToken);
});
handleAccount("bedrockV2", object, account -> bedrockAccount = BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.fromJson(account));
handleAccount("classicube", object, account -> classicubeAccount = CCAccount.fromJson(account));
} }
}
if (object.has("classicube")) { private void handleAccount(final String name, final JsonObject object, final AccountConsumer output) {
if (object.has(name)) {
try { try {
classicubeAccount = CCAccount.fromJson(object.get("classicube").getAsJsonObject()); output.accept(object.get(name).getAsJsonObject());
} catch (Exception e) { } catch (Exception e) {
ViaFabricPlus.global().getLogger().error("Failed to read classicube account!", e); ViaFabricPlus.global().getLogger().error("Failed to read {} account!", name, e);
} }
} }
} }
public StepFullBedrockSession.FullBedrockSession refreshAndGetBedrockAccount() { public StepFullBedrockSession.FullBedrockSession refreshAndGetBedrockAccount() {
if (bedrockAccount == null) return null; if (bedrockAccount == null) {
return null;
}
try { try {
bedrockAccount = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.refresh(MinecraftAuth.createHttpClient(), bedrockAccount); bedrockAccount = BedrockSettings.BEDROCK_DEVICE_CODE_LOGIN.refresh(MinecraftAuth.createHttpClient(), bedrockAccount);
} catch (Throwable t) { } catch (Throwable t) {
throw new RuntimeException("Failed to refresh Bedrock chain data. Please re-login to Bedrock!", t); throw new RuntimeException("Failed to refresh Bedrock chain data. Please re-login to Bedrock!", t);
} }
return bedrockAccount; return bedrockAccount;
} }
@ -91,4 +100,11 @@ public class AccountsSave extends AbstractSave {
this.classicubeAccount = classicubeAccount; this.classicubeAccount = classicubeAccount;
} }
@FunctionalInterface
interface AccountConsumer {
void accept(JsonObject account) throws Exception;
}
} }

View File

@ -62,18 +62,23 @@ public abstract class VFPListEntry extends AlwaysSelectedEntryListWidget.Entry<V
return super.mouseClicked(mouseX, mouseY, button); return super.mouseClicked(mouseX, mouseY, button);
} }
public void renderScrollableText(final Text name, final int offset) {
final var font = MinecraftClient.getInstance().textRenderer;
renderScrollableText(name, entryHeight / 2 - font.fontHeight / 2, offset);
}
/** /**
* Automatically scrolls the text if it is too long to be displayed in the slot. The text will be scrolled from right to left * Automatically scrolls the text if it is too long to be displayed in the slot. The text will be scrolled from right to left
* *
* @param name The text which should be displayed * @param name The text which should be displayed
* @param textY The Y position of the text
* @param offset The offset of the text from the left side of the slot, this is used to calculate the width of the text, which should be scrolled (Scrolling is enabled when entryWidth - offset < textWidth) * @param offset The offset of the text from the left side of the slot, this is used to calculate the width of the text, which should be scrolled (Scrolling is enabled when entryWidth - offset < textWidth)
*/ */
public void renderScrollableText(final Text name, final int offset) { public void renderScrollableText(final Text name, final int textY, final int offset) {
final var font = MinecraftClient.getInstance().textRenderer; final var font = MinecraftClient.getInstance().textRenderer;
final int fontWidth = font.getWidth(name); final int fontWidth = font.getWidth(name);
final int textY = entryHeight / 2 - font.fontHeight / 2;
if (fontWidth > (entryWidth - offset)) { if (fontWidth > (entryWidth - offset)) {
final double time = (double) Util.getMeasuringTimeMs() / 1000.0; final double time = (double) Util.getMeasuringTimeMs() / 1000.0;
final double interpolateEnd = fontWidth - (entryWidth - offset - (SCISSORS_OFFSET + SLOT_MARGIN)); final double interpolateEnd = fontWidth - (entryWidth - offset - (SCISSORS_OFFSET + SLOT_MARGIN));

View File

@ -163,10 +163,16 @@ public class VFPScreen extends Screen {
*/ */
public void renderSubtitle(final DrawContext context) { public void renderSubtitle(final DrawContext context) {
if (subtitle != null && subtitlePressAction == null) { if (subtitle != null && subtitlePressAction == null) {
context.drawCenteredTextWithShadow(textRenderer, subtitle, width / 2, (textRenderer.fontHeight + 2) * 2 + 3, -1); final int startY = (textRenderer.fontHeight + 2) * 2 + 3;
context.drawCenteredTextWithShadow(textRenderer, subtitle, width / 2, subtitleCentered() ? this.height / 2 - startY : startY, -1);
} }
} }
protected boolean subtitleCentered() {
// To be overriden
return false;
}
/** /**
* Plays Minecraft's button click sound * Plays Minecraft's button click sound
*/ */

View File

@ -25,6 +25,7 @@ import de.florianmichael.viafabricplus.screen.VFPScreen;
import de.florianmichael.viafabricplus.screen.classic4j.BetaCraftScreen; import de.florianmichael.viafabricplus.screen.classic4j.BetaCraftScreen;
import de.florianmichael.viafabricplus.screen.classic4j.ClassiCubeLoginScreen; import de.florianmichael.viafabricplus.screen.classic4j.ClassiCubeLoginScreen;
import de.florianmichael.viafabricplus.screen.classic4j.ClassiCubeServerListScreen; import de.florianmichael.viafabricplus.screen.classic4j.ClassiCubeServerListScreen;
import de.florianmichael.viafabricplus.screen.realms.BedrockRealmsScreen;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.tooltip.Tooltip;
import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ButtonWidget;
@ -78,6 +79,20 @@ public class ServerListScreen extends VFPScreen {
betaCraftBuilder = betaCraftBuilder.tooltip(Tooltip.of(Text.translatable("betacraft.viafabricplus.warning"))); betaCraftBuilder = betaCraftBuilder.tooltip(Tooltip.of(Text.translatable("betacraft.viafabricplus.warning")));
} }
this.addDrawableChild(betaCraftBuilder.build()); this.addDrawableChild(betaCraftBuilder.build());
ButtonWidget.Builder bedrockRealmsBuilder = ButtonWidget.builder(BedrockRealmsScreen.INSTANCE.getTitle(), button -> {
BedrockRealmsScreen.INSTANCE.open(this);
}).position(this.width / 2 - 50, this.height / 2 - 25 + 40 + 6).size(98, 20);
final boolean missingAccount = ViaFabricPlus.global().getSaveManager().getAccountsSave().getBedrockAccount() == null; // Only check for presence, later validate
if (missingAccount) {
bedrockRealmsBuilder.tooltip(Tooltip.of(Text.translatable("bedrock_realms.viafabricplus.warning")));
}
ButtonWidget bedrockRealmsButton = bedrockRealmsBuilder.build();
this.addDrawableChild(bedrockRealmsButton);
if (missingAccount) {
bedrockRealmsButton.active = false;
}
} }
@Override @Override

View File

@ -26,15 +26,13 @@ import de.florianmichael.viafabricplus.screen.VFPList;
import de.florianmichael.viafabricplus.screen.VFPListEntry; import de.florianmichael.viafabricplus.screen.VFPListEntry;
import de.florianmichael.viafabricplus.screen.VFPScreen; import de.florianmichael.viafabricplus.screen.VFPScreen;
import de.florianmichael.viafabricplus.screen.settings.TitleRenderer; import de.florianmichael.viafabricplus.screen.settings.TitleRenderer;
import de.florianmichael.viafabricplus.util.ConnectionUtil;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ConfirmLinkScreen; import net.minecraft.client.gui.screen.ConfirmLinkScreen;
import net.minecraft.client.gui.screen.multiplayer.ConnectScreen;
import net.minecraft.client.gui.screen.option.ControlsListWidget; import net.minecraft.client.gui.screen.option.ControlsListWidget;
import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.network.ServerInfo;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
@ -66,7 +64,6 @@ public class BetaCraftScreen extends VFPScreen {
@Override @Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) { public void render(DrawContext context, int mouseX, int mouseY, float delta) {
this.renderBackground(context, mouseX, mouseY, delta);
super.render(context, mouseX, mouseY, delta); super.render(context, mouseX, mouseY, delta);
this.renderTitle(context); this.renderTitle(context);
@ -116,10 +113,7 @@ public class BetaCraftScreen extends VFPScreen {
@Override @Override
public void mappedMouseClicked(double mouseX, double mouseY, int button) { public void mappedMouseClicked(double mouseX, double mouseY, int button) {
final ServerAddress serverAddress = ServerAddress.parse(server.socket()); ConnectionUtil.connect(server.name(), server.socket());
final ServerInfo entry = new ServerInfo(server.name(), serverAddress.getAddress(), ServerInfo.ServerType.OTHER);
ConnectScreen.connect(MinecraftClient.getInstance().currentScreen, MinecraftClient.getInstance(), serverAddress, entry, false, null);
super.mappedMouseClicked(mouseX, mouseY, button); super.mappedMouseClicked(mouseX, mouseY, button);
} }

View File

@ -23,20 +23,16 @@ import de.florianmichael.classic4j.ClassiCubeHandler;
import de.florianmichael.classic4j.api.LoginProcessHandler; import de.florianmichael.classic4j.api.LoginProcessHandler;
import de.florianmichael.classic4j.model.classicube.server.CCServerInfo; import de.florianmichael.classic4j.model.classicube.server.CCServerInfo;
import de.florianmichael.viafabricplus.ViaFabricPlus; import de.florianmichael.viafabricplus.ViaFabricPlus;
import de.florianmichael.viafabricplus.injection.access.IServerInfo;
import de.florianmichael.viafabricplus.protocoltranslator.impl.provider.vialegacy.ViaFabricPlusClassicMPPassProvider;
import de.florianmichael.viafabricplus.screen.VFPList; import de.florianmichael.viafabricplus.screen.VFPList;
import de.florianmichael.viafabricplus.screen.VFPListEntry; import de.florianmichael.viafabricplus.screen.VFPListEntry;
import de.florianmichael.viafabricplus.screen.VFPScreen; import de.florianmichael.viafabricplus.screen.VFPScreen;
import de.florianmichael.viafabricplus.settings.impl.AuthenticationSettings; import de.florianmichael.viafabricplus.settings.impl.AuthenticationSettings;
import de.florianmichael.viafabricplus.util.ConnectionUtil;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.multiplayer.ConnectScreen;
import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.network.ServerInfo;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.raphimc.vialegacy.api.LegacyProtocolVersion; import net.raphimc.vialegacy.api.LegacyProtocolVersion;
@ -131,16 +127,8 @@ public class ClassiCubeServerListScreen extends VFPScreen {
@Override @Override
public void mappedMouseClicked(double mouseX, double mouseY, int button) { public void mappedMouseClicked(double mouseX, double mouseY, int button) {
final ServerAddress serverAddress = ServerAddress.parse(classiCubeServerInfo.ip() + ":" + classiCubeServerInfo.port()); final boolean selectCPE = AuthenticationSettings.global().automaticallySelectCPEInClassiCubeServerList.getValue();
final ServerInfo entry = new ServerInfo(classiCubeServerInfo.name(), serverAddress.getAddress(), ServerInfo.ServerType.OTHER); ConnectionUtil.connect(classiCubeServerInfo.name(), classiCubeServerInfo.ip() + ":" + classiCubeServerInfo.port(), selectCPE ? LegacyProtocolVersion.c0_30cpe : null);
ViaFabricPlusClassicMPPassProvider.classicubeMPPass = classiCubeServerInfo.mpPass();
if (AuthenticationSettings.global().automaticallySelectCPEInClassiCubeServerList.getValue()) {
((IServerInfo) entry).viaFabricPlus$forceVersion(LegacyProtocolVersion.c0_30cpe);
}
ConnectScreen.connect(MinecraftClient.getInstance().currentScreen, MinecraftClient.getInstance(), serverAddress, entry, false, null);
super.mappedMouseClicked(mouseX, mouseY, button);
} }
@Override @Override

View File

@ -0,0 +1,63 @@
/*
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
* Copyright (C) 2021-2024 FlorianMichael/EnZaXD <florian.michael07@gmail.com> and RK_01/RaphiMC
* Copyright (C) 2023-2024 contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.florianmichael.viafabricplus.screen.realms;
import de.florianmichael.viafabricplus.screen.VFPScreen;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
import java.util.function.Consumer;
public class AcceptInvitationCodeScreen extends VFPScreen {
private final Consumer<String> serviceHandler;
public AcceptInvitationCodeScreen(Consumer<String> serviceHandler) {
super(Text.translatable("screen.viafabricplus.accept_invite"), true);
this.serviceHandler = serviceHandler;
}
@Override
protected void init() {
super.init();
setupDefaultSubtitle();
final TextFieldWidget codeField = new TextFieldWidget(textRenderer, this.width / 2 - 100, this.height / 2 - 10, 200, 20, Text.empty());
codeField.setPlaceholder(Text.translatable("base.viafabricplus.code"));
this.addDrawableChild(codeField);
this.addDrawableChild(ButtonWidget.builder(Text.translatable("base.viafabricplus.accept"), button -> {
this.serviceHandler.accept(codeField.getText());
close();
}).position(this.width / 2 - ButtonWidget.DEFAULT_WIDTH / 2, this.height / 2 + 20).build());
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
this.renderTitle(context);
}
}

View File

@ -0,0 +1,243 @@
/*
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
* Copyright (C) 2021-2024 FlorianMichael/EnZaXD <florian.michael07@gmail.com> and RK_01/RaphiMC
* Copyright (C) 2023-2024 contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.florianmichael.viafabricplus.screen.realms;
import de.florianmichael.viafabricplus.ViaFabricPlus;
import de.florianmichael.viafabricplus.screen.VFPList;
import de.florianmichael.viafabricplus.screen.VFPListEntry;
import de.florianmichael.viafabricplus.screen.VFPScreen;
import de.florianmichael.viafabricplus.util.ConnectionUtil;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.option.ControlsListWidget;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.text.Text;
import net.raphimc.minecraftauth.MinecraftAuth;
import net.raphimc.minecraftauth.service.realms.BedrockRealmsService;
import net.raphimc.minecraftauth.service.realms.model.RealmsWorld;
import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession;
import net.raphimc.viabedrock.api.BedrockProtocolVersion;
import net.raphimc.viabedrock.protocol.data.ProtocolConstants;
import org.apache.logging.log4j.Level;
import java.awt.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class BedrockRealmsScreen extends VFPScreen {
public static final BedrockRealmsScreen INSTANCE = new BedrockRealmsScreen();
private BedrockRealmsService service;
private List<RealmsWorld> realmsWorlds;
private SlotList slotList;
private ButtonWidget joinButton;
private ButtonWidget leaveButton;
public BedrockRealmsScreen() {
super(Text.translatable("screen.viafabricplus.bedrock_realms"), true);
}
@Override
protected void init() {
super.init();
if (realmsWorlds != null) {
createView();
return;
}
setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.availability_check"));
CompletableFuture.runAsync(this::loadRealms);
}
private void loadRealms() {
final StepFullBedrockSession.FullBedrockSession account = ViaFabricPlus.global().getSaveManager().getAccountsSave().refreshAndGetBedrockAccount();
if (account == null) { // Just in case...
setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.warning"));
return;
}
service = new BedrockRealmsService(MinecraftAuth.createHttpClient(), ProtocolConstants.BEDROCK_VERSION_NAME, account.getRealmsXsts());
service.isAvailable().thenAccept(state -> {
if (state) {
service.getWorlds().thenAccept(realmsWorlds -> {
this.realmsWorlds = realmsWorlds;
createView();
}).exceptionally(throwable -> error("Failed to load realm worlds", throwable));
} else {
setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.unavailable"));
}
}).exceptionally(throwable -> error("Failed to check realms availability", throwable));
}
private Void error(final String message, final Throwable throwable) {
setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.error"));
ViaFabricPlus.global().getLogger().log(Level.ERROR, message, throwable);
return null;
}
private void createView() {
if (!this.realmsWorlds.isEmpty()) {
setupDefaultSubtitle();
} else {
setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.no_worlds"));
}
this.addDrawableChild(slotList = new SlotList(this.client, width, height, 3 + 3 /* start offset */ + (textRenderer.fontHeight + 2) * 3 /* title is 2 */, 30, (textRenderer.fontHeight + 2) * 4));
this.addDrawableChild(ButtonWidget.builder(ControlsListWidget.KeyBindingEntry.RESET_TEXT, button -> {
realmsWorlds = null;
client.setScreen(this);
}).position(width - 98 - 5, 5).size(98, 20).build());
final int slotWidth = 360 - 4;
int xPos = width / 2 - slotWidth / 2;
this.addDrawableChild(joinButton = ButtonWidget.builder(Text.translatable("bedrock_realms.viafabricplus.join"), button -> {
final SlotEntry entry = (SlotEntry) slotList.getFocused();
if (entry.realmsWorld.isExpired()) {
setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.expired"));
return;
} else if (!entry.realmsWorld.isCompatible()) {
setupSubtitle(Text.translatable("bedrock_realms.viafabricplus.incompatible"));
return;
}
service.joinWorld(entry.realmsWorld).thenAccept(address -> {
client.execute(() -> ConnectionUtil.connect(address, BedrockProtocolVersion.bedrockLatest));
}).exceptionally(throwable -> error("Failed to join realm", throwable));
}).position(xPos, height - 20 - 5).size(115, 20).build());
joinButton.active = false;
xPos += 115 + 5;
this.addDrawableChild(leaveButton = ButtonWidget.builder(Text.translatable("bedrock_realms.viafabricplus.leave"), button -> {
final SlotEntry entry = (SlotEntry) slotList.getFocused();
service.leaveInvitedRealm(entry.realmsWorld).thenAccept(unused -> {
this.realmsWorlds.remove(entry.realmsWorld);
INSTANCE.open(prevScreen);
}).exceptionally(throwable -> error("Failed to leave realm", throwable));
}).position(xPos, height - 20 - 5).size(115, 20).build());
leaveButton.active = false;
xPos += 115 + 5;
this.addDrawableChild(ButtonWidget.builder(Text.translatable("bedrock_realms.viafabricplus.invite"), button -> {
final AcceptInvitationCodeScreen screen = new AcceptInvitationCodeScreen(code -> service.acceptInvite(code).thenAccept(world -> {
this.realmsWorlds.add(world);
INSTANCE.open(this);
}).exceptionally(throwable -> error("Failed to accept invite", throwable)));
screen.open(this);
}).position(xPos, height - 20 - 5).size(115, 20).build());
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
this.renderTitle(context);
}
@Override
public void tick() {
super.tick();
if (slotList != null && joinButton != null && leaveButton != null) {
joinButton.active = slotList.getFocused() instanceof SlotEntry;
leaveButton.active = slotList.getFocused() instanceof SlotEntry;
}
}
@Override
protected boolean subtitleCentered() {
return slotList == null;
}
public class SlotList extends VFPList<VFPListEntry> {
private static double scrollAmount;
public SlotList(MinecraftClient minecraftClient, int width, int height, int top, int bottom, int entryHeight) {
super(minecraftClient, width, height, top, bottom, entryHeight);
for (RealmsWorld realmsWorld : BedrockRealmsScreen.this.realmsWorlds) {
this.addEntry(new SlotEntry(this, realmsWorld));
}
initScrollAmount(scrollAmount);
}
@Override
protected void updateSlotAmount(double amount) {
scrollAmount = amount;
}
@Override
public int getRowWidth() {
return super.getRowWidth() + 140;
}
}
public class SlotEntry extends VFPListEntry {
private final SlotList slotList;
private final RealmsWorld realmsWorld;
public SlotEntry(SlotList slotList, RealmsWorld realmsWorld) {
this.slotList = slotList;
this.realmsWorld = realmsWorld;
}
@Override
public Text getNarration() {
return Text.of(realmsWorld.getName());
}
@Override
public void mappedRender(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
String name = "";
final String ownerName = realmsWorld.getOwnerName();
if (ownerName != null && !ownerName.trim().isEmpty()) {
name += ownerName + " - ";
}
final String worldName = realmsWorld.getName();
if (worldName != null && !worldName.trim().isEmpty()) {
name += worldName;
}
name += " (" + realmsWorld.getState() + ")";
context.drawTextWithShadow(textRenderer, name, 3, 3, slotList.getFocused() == this ? Color.ORANGE.getRGB() : -1);
String version = realmsWorld.getWorldType();
final String activeVersion = realmsWorld.getActiveVersion();
if (activeVersion != null && !activeVersion.trim().isEmpty()) {
version += " - " + activeVersion;
}
context.drawTextWithShadow(textRenderer, version, entryWidth - textRenderer.getWidth(version) - 4 - 3, 3, -1);
final String motd = realmsWorld.getMotd();
if (motd != null) {
renderScrollableText(Text.of(motd), entryHeight - textRenderer.fontHeight - 3, 3 * 2);
}
}
}
}

View File

@ -34,8 +34,10 @@ import net.minecraft.text.Text;
import net.minecraft.util.Util; import net.minecraft.util.Util;
import net.raphimc.minecraftauth.MinecraftAuth; import net.raphimc.minecraftauth.MinecraftAuth;
import net.raphimc.minecraftauth.step.AbstractStep; import net.raphimc.minecraftauth.step.AbstractStep;
import net.raphimc.minecraftauth.step.bedrock.session.StepFullBedrockSession;
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode; import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCodeMsaCode; import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCodeMsaCode;
import net.raphimc.minecraftauth.util.MicrosoftConstants;
import net.raphimc.minecraftauth.util.logging.ConsoleLogger; import net.raphimc.minecraftauth.util.logging.ConsoleLogger;
import net.raphimc.minecraftauth.util.logging.ILogger; import net.raphimc.minecraftauth.util.logging.ILogger;
@ -47,6 +49,13 @@ public class BedrockSettings extends SettingGroup {
private static final BedrockSettings INSTANCE = new BedrockSettings(); private static final BedrockSettings INSTANCE = new BedrockSettings();
public static final AbstractStep<?, StepFullBedrockSession.FullBedrockSession> BEDROCK_DEVICE_CODE_LOGIN = MinecraftAuth.builder()
.withClientId(MicrosoftConstants.BEDROCK_ANDROID_TITLE_ID).withScope(MicrosoftConstants.SCOPE_TITLE_AUTH)
.deviceCode()
.withDeviceToken("Android")
.sisuTitleAuthentication(MicrosoftConstants.BEDROCK_XSTS_RELYING_PARTY)
.buildMinecraftBedrockChainStep(true, true);
private Thread thread; private Thread thread;
private final ButtonSetting clickToSetBedrockAccount = new ButtonSetting(this, Text.translatable("bedrock_settings.viafabricplus.click_to_set_bedrock_account"), () -> { private final ButtonSetting clickToSetBedrockAccount = new ButtonSetting(this, Text.translatable("bedrock_settings.viafabricplus.click_to_set_bedrock_account"), () -> {
thread = new Thread(this::openBedrockAccountLogin); thread = new Thread(this::openBedrockAccountLogin);
@ -90,7 +99,7 @@ public class BedrockSettings extends SettingGroup {
final MinecraftClient client = MinecraftClient.getInstance(); final MinecraftClient client = MinecraftClient.getInstance();
final Screen prevScreen = client.currentScreen; final Screen prevScreen = client.currentScreen;
try { try {
accountsSave.setBedrockAccount(MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.getFromInput(GUI_LOGGER, MinecraftAuth.createHttpClient(), new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCode -> { accountsSave.setBedrockAccount(BEDROCK_DEVICE_CODE_LOGIN.getFromInput(GUI_LOGGER, MinecraftAuth.createHttpClient(), new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCode -> {
VFPScreen.setScreen(new ConfirmScreen(copyUrl -> { VFPScreen.setScreen(new ConfirmScreen(copyUrl -> {
if (copyUrl) { if (copyUrl) {
client.keyboard.setClipboard(msaDeviceCode.getDirectVerificationUri()); client.keyboard.setClipboard(msaDeviceCode.getDirectVerificationUri());

View File

@ -0,0 +1,49 @@
/*
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
* Copyright (C) 2021-2024 FlorianMichael/EnZaXD <florian.michael07@gmail.com> and RK_01/RaphiMC
* Copyright (C) 2023-2024 contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.florianmichael.viafabricplus.util;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import de.florianmichael.viafabricplus.injection.access.IServerInfo;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.multiplayer.ConnectScreen;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.network.ServerInfo;
public class ConnectionUtil {
public static void connect(final String address, final ProtocolVersion version) {
connect(address, address, version);
}
public static void connect(final String name, final String address) {
connect(name, address, null);
}
public static void connect(final String name, final String address, final ProtocolVersion version) {
final ServerAddress serverAddress = ServerAddress.parse(address);
final ServerInfo entry = new ServerInfo(name, serverAddress.getAddress(), ServerInfo.ServerType.OTHER);
if (version != null) {
((IServerInfo) entry).viaFabricPlus$forceVersion(version);
}
ConnectScreen.connect(MinecraftClient.getInstance().currentScreen, MinecraftClient.getInstance(), serverAddress, entry, false, null);
}
}

View File

@ -28,6 +28,8 @@
"base.viafabricplus.name": "Name", "base.viafabricplus.name": "Name",
"base.viafabricplus.password": "Password", "base.viafabricplus.password": "Password",
"base.viafabricplus.login": "Login", "base.viafabricplus.login": "Login",
"base.viafabricplus.code": "Code",
"base.viafabricplus.accept": "Accept",
"setting_group_name.viafabricplus.authentication": "Authentication", "setting_group_name.viafabricplus.authentication": "Authentication",
"setting_group_name.viafabricplus.visual": "Visual", "setting_group_name.viafabricplus.visual": "Visual",
@ -41,6 +43,8 @@
"screen.viafabricplus.classicube_login": "Classicube Login", "screen.viafabricplus.classicube_login": "Classicube Login",
"screen.viafabricplus.classicube_mfa": "Classicube MFA", "screen.viafabricplus.classicube_mfa": "Classicube MFA",
"screen.viafabricplus.settings": "Settings", "screen.viafabricplus.settings": "Settings",
"screen.viafabricplus.bedrock_realms": "Bedrock Realms",
"screen.viafabricplus.accept_invite": "Accept Invite",
"general_settings.viafabricplus.save_selected_protocol_version": "Save selected protocol version", "general_settings.viafabricplus.save_selected_protocol_version": "Save selected protocol version",
"general_settings.viafabricplus.extra_information_in_debug_hud": "Show extra information in Debug HUD", "general_settings.viafabricplus.extra_information_in_debug_hud": "Show extra information in Debug HUD",
@ -147,5 +151,17 @@
"minecraftauth_library.viafabricplus.xblsisuauthentication": "Authenticating with Xbox Live using SISU...", "minecraftauth_library.viafabricplus.xblsisuauthentication": "Authenticating with Xbox Live using SISU...",
"minecraftauth_library.viafabricplus.mcchain": "Authenticating with Minecraft Services...", "minecraftauth_library.viafabricplus.mcchain": "Authenticating with Minecraft Services...",
"minecraftauth_library.viafabricplus.xblxststoken": "Requesting XSTS Token...", "minecraftauth_library.viafabricplus.xblxststoken": "Requesting XSTS Token...",
"minecraftauth_library.viafabricplus.playfabtoken": "Authenticating with PlayFab..." "minecraftauth_library.viafabricplus.playfabtoken": "Authenticating with PlayFab...",
"minecraftauth_library.viafabricplus.realmsxsts": "Requesting Realms XSTS Token...",
"bedrock_realms.viafabricplus.warning": "This feature requires a Bedrock Edition account to be set in the settings!",
"bedrock_realms.viafabricplus.availability_check": "Checking availability...",
"bedrock_realms.viafabricplus.no_worlds": "No worlds found :(",
"bedrock_realms.viafabricplus.unavailable": "Your ViaFabricPlus is outdated and not compatible with Minecraft Realms.",
"bedrock_realms.viafabricplus.error": "An error occurred, see the logs for more information",
"bedrock_realms.viafabricplus.join": "Join realm",
"bedrock_realms.viafabricplus.leave": "Leave realm",
"bedrock_realms.viafabricplus.invite": "Accept invite code",
"bedrock_realms.viafabricplus.expired": "The Realm is expired",
"bedrock_realms.viafabricplus.incompatible": "The Realms Minecraft version is either too old or too new"
} }