mirror of
https://github.com/ViaVersion/ViaFabricPlus.git
synced 2024-11-25 12:25:22 +01:00
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:
parent
2c45810f4f
commit
9ed44d681f
@ -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 -> {
|
||||||
|
// Use old login flow, then get refresh token and login via new flow
|
||||||
|
final StepFullBedrockSession.FullBedrockSession oldSession = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.fromJson(account);
|
||||||
|
final StepInitialXblSession.InitialXblSession xblSession = oldSession.getMcChain().getXblXsts().getInitialXblSession();
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAccount(final String name, final JsonObject object, final AccountConsumer output) {
|
||||||
|
if (object.has(name)) {
|
||||||
try {
|
try {
|
||||||
bedrockAccount = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.fromJson(object.get("bedrock").getAsJsonObject());
|
output.accept(object.get(name).getAsJsonObject());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ViaFabricPlus.global().getLogger().error("Failed to read bedrock account!", e);
|
ViaFabricPlus.global().getLogger().error("Failed to read {} account!", name, e);
|
||||||
}
|
|
||||||
}
|
|
||||||
if (object.has("classicube")) {
|
|
||||||
try {
|
|
||||||
classicubeAccount = CCAccount.fromJson(object.get("classicube").getAsJsonObject());
|
|
||||||
} catch (Exception e) {
|
|
||||||
ViaFabricPlus.global().getLogger().error("Failed to read classicube account!", 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user