summary refs log blame commit diff stats
path: root/src/main/java/ganarchy/friendcode/client/FriendCodeScreen.java
blob: 9c96bee27256ed874dd9b20f076e09afbfe5f6cb (plain) (tree)
1
2
3
4
5
6



                                                               

                                        











                                                           

                                        




                                              





                                                                             
                                
                                                 


                                                  



















                                                                             

















                                                             


                               










                                                           

          


                                  













                                                             
 







                                               
                                                     









                                                                            

                                                                    


                                                                     













                                                        



                                                                     
             
                                            

                                             


                                                                  
                                                                 
                                                          
             
                                   
                                                   
                                                                  
                                                                   
                                                                    



                                                                          
                              


                                                                              
                                                      

                                                                          

                             
                                           




                        


                                                                 
                                        



                                                            


                                                      
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);
    }
}