package ganarchy.friendcode.client;
import ganarchy.friendcode.FriendCode;
import ganarchy.friendcode.mixin.FriendCodeIntegratedServerExt;
import ganarchy.friendcode.util.KeyUtil;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.OpenToLanScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.CyclingButtonWidget;
import net.minecraft.client.util.NetworkUtils;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.ScreenTexts;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.WorldSavePath;
import net.minecraft.world.GameMode;
import java.io.IOException;
@Environment(EnvType.CLIENT)
public class FriendCodeScreen extends Screen {
private static final Text
ALLOW_COMMANDS_TEXT = Text.translatable("selectWorld.allowCommands"),
GAME_MODE_TEXT = Text.translatable("selectWorld.gameMode"),
CODE_TYPE_TEXT = Text.translatable("friendcode.code_type"),
OTHER_PLAYERS_TEXT = Text.translatable("lanServer.otherPlayers"),
START_SHARING = Text.translatable("friendcode.start");
private final Screen parent;
private CodeType codeType = CodeType.SESSION;
private GameMode gameMode = GameMode.SURVIVAL;
private boolean allowCommands;
public FriendCodeScreen(Screen parent) {
super(Text.translatable("friendcode.title"));
this.parent = parent;
}
@Override
protected void init() {
// title button allows going back to LAN screen
this.addDrawableChild(new ButtonWidget(
this.width / 2 - 155,
50,
310,
20,
this.title,
button -> this.client.setScreen(new OpenToLanScreen(this.parent))
));
// game setting buttons
this.addDrawableChild(
CyclingButtonWidget
.builder(GameMode::getSimpleTranslatableName)
.values(
GameMode.SURVIVAL,
GameMode.SPECTATOR,
GameMode.CREATIVE,
GameMode.ADVENTURE
)
.initially(this.gameMode)
.build(
this.width / 2 - 155,
100,
150,
20,
GAME_MODE_TEXT,
(button, gameMode) -> {
this.gameMode = gameMode;
}
)
);
this.addDrawableChild(
CyclingButtonWidget
.onOffBuilder(this.allowCommands)
.build(
this.width / 2 + 5,
100,
150,
20,
ALLOW_COMMANDS_TEXT,
(button, allowCommands) -> {
this.allowCommands = allowCommands;
}
)
);
// friend code type button
this.addDrawableChild(
CyclingButtonWidget
.builder(CodeType::getSimpleTranslatableName)
.values(CodeType.SESSION, CodeType.WORLD)
.initially(this.codeType)
.build(
this.width / 2 - 155,
125,
310,
20,
CODE_TYPE_TEXT,
(button, codeType) -> {
this.codeType = codeType;
}
)
);
// start sharing
this.addDrawableChild(new ButtonWidget(
this.width / 2 - 155,
this.height - 28,
150,
20,
START_SHARING,
button -> {
int i = NetworkUtils.findLocalPort();
var samPinger = openToFriends(
this.client, this.codeType, this.gameMode,
this.allowCommands, i
);
MutableText text;
if (samPinger != null) {
text = Text.translatable("commands.publish.started", i);
} else {
text = Text.translatable("commands.publish.failed");
}
this.client.inGameHud.getChatHud().addMessage(text);
this.client.updateWindowTitle();
this.client.setScreen(new WaitingForFriendCodeScreen(
this.codeType, samPinger
));
}
));
// go back to options
this.addDrawableChild(new ButtonWidget(
this.width / 2 + 5,
this.height - 28,
150,
20,
ScreenTexts.CANCEL,
button -> this.client.setScreen(this.parent)
));
}
private static I2PSamPinger openToFriends(
MinecraftClient client, CodeType codeType, GameMode gameMode,
boolean allowCommands, int port
) {
try {
var server = client.getServer();
String privateKey = null;
if (codeType == CodeType.WORLD) {
var worldDir = server.submit(() -> {
return server.getSavePath(WorldSavePath.ROOT);
}).join();
var keyFile = worldDir.resolve("friendcode.key");
privateKey = KeyUtil.readKeyFile(keyFile);
}
client.loadBlockList();
server.getNetworkIo().bind(null, port);
FriendCode.LOGGER.info("Started serving on {}", port);
((FriendCodeIntegratedServerExt) server).lanPort(port);
// reuse LAN pinger machinery instead of rolling our own
var lanPinger = new I2PSamPinger(
server.getServerMotd(), "" + port, privateKey
);
((FriendCodeIntegratedServerExt) server).lanPinger(lanPinger);
lanPinger.start();
((FriendCodeIntegratedServerExt) server).forcedGameMode(gameMode);
server.getPlayerManager().setCheatsAllowed(allowCommands);
int i = server.getPermissionLevel(client.player.getGameProfile());
client.player.setClientPermissionLevel(i);
for (var player : server.getPlayerManager().getPlayerList()) {
server.getCommandManager().sendCommandTree(player);
}
return lanPinger;
} catch (IOException iOException) {
return null;
}
}
@Override
public void render(
MatrixStack matrices, int mouseX, int mouseY, float delta
) {
this.renderBackground(matrices);
FriendCodeScreen.drawCenteredText(
matrices, this.textRenderer, OTHER_PLAYERS_TEXT,
this.width / 2, 82, 0xFFFFFF
);
super.render(matrices, mouseX, mouseY, delta);
}
}