aphelion.client.Client.java Source code

Java tutorial

Introduction

Here is the source code for aphelion.client.Client.java

Source

/*
 * Aphelion
 * Copyright (c) 2013  Joris van der Wel
 * 
 * This file is part of Aphelion
 * 
 * Aphelion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 * 
 * Aphelion 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 Affero General Public License
 * along with Aphelion.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * In addition, the following supplemental terms apply, based on section 7 of
 * the GNU Affero General Public License (version 3):
 * a) Preservation of all legal notices and author attributions
 * b) Prohibition of misrepresentation of the origin of this material, and
 * modified versions are required to be marked in reasonable ways as
 * different from the original version (for example by appending a copyright notice).
 * 
 * Linking this library statically or dynamically with other modules is making a
 * combined work based on this library. Thus, the terms and conditions of the
 * GNU Affero General Public License cover the whole combination.
 * 
 * As a special exception, the copyright holders of this library give you 
 * permission to link this library with independent modules to produce an 
 * executable, regardless of the license terms of these independent modules,
 * and to copy and distribute the resulting executable under terms of your 
 * choice, provided that you also meet, for each linked independent module,
 * the terms and conditions of the license of that module. An independent
 * module is a module which is not derived from or based on this library.
 */
package aphelion.client;

import aphelion.server.AphelionServerThread;
import aphelion.client.graphics.Graph;
import aphelion.client.net.NetworkedGame;
import aphelion.client.resource.AsyncTexture;
import aphelion.server.ServerConfigException;
import aphelion.shared.event.Deadlock;
import aphelion.shared.resource.ResourceDB;
import aphelion.shared.event.TickedEventLoop;
import aphelion.shared.net.WS_CLOSE_STATUS;
import aphelion.shared.net.protobuf.GameS2C.AuthenticateResponse;
import aphelion.shared.physics.EnvironmentConf;
import aphelion.shared.resource.LocalUserStorage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.Graphics;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.YAMLException;

/**
 *
 * @author Joris
 */
public class Client {
    private static final Logger log = Logger.getLogger("aphelion.client");
    public static boolean showDebug = false;
    private TickedEventLoop loop;
    private ResourceDB resourceDB;
    private AphelionServerThread serverThread;
    private ConnectLoop connectLoop;
    private STATE state = STATE.NONE;
    private URI serverUri;

    private static enum STATE {
        NONE, CONNECTING, PLAYING, QUITTING
    }

    /** *  @param uri if null, singleplayer
     * @param nickname 
     * @throws LWJGLException
     * @throws IOException
     * @throws aphelion.server.ServerConfigException
     */
    public void run(@Nullable URI uri, @Nonnull final String nickname)
            throws LWJGLException, IOException, ServerConfigException {
        if (uri == null) {
            Yaml yaml = new Yaml(new SafeConstructor());
            Map<String, Object> singlePlayerConfig;
            try {
                singlePlayerConfig = (Map<String, Object>) yaml
                        .load(new FileInputStream("./assets/singleplayer.yaml"));
            } catch (FileNotFoundException | ClassCastException | YAMLException ex) {
                // Note: YAMLException is a RunTimeException
                throw new ServerConfigException("Unable to read server config", ex);
            }

            // ignore bind-address and bind-port

            // share the assets directory
            singlePlayerConfig.put("assets-cache-path",
                    new LocalUserStorage("assets").getDirectory().getAbsolutePath());

            serverThread = new AphelionServerThread(false, singlePlayerConfig);
            serverThread.start();
            try {
                uri = new URI("ws://127.0.0.1:" + serverThread.getHTTPListeningPort() + "/aphelion");
            } catch (URISyntaxException ex) {
                log.log(Level.SEVERE, "Malformed URI", ex);
                throw new Error(ex);
            }
        }
        this.serverUri = uri;

        Display.setTitle("Aphelion");

        ByteBuffer[] icons = new ByteBuffer[6];
        aphelion.launcher.Main.getFrameIcons(icons);
        Display.setIcon(icons);
        Display.setFullscreen(false);
        Display.setVSyncEnabled(false);
        Display.setResizable(true);
        Display.setInitialBackground(0f, 0f, 0f);
        Display.setDisplayMode(new DisplayMode(1024, 768));
        Display.create();

        log.log(Level.INFO, "OpenGL version: {0}", GL11.glGetString(GL11.GL_VERSION));

        Keyboard.create();

        // use the default time source for now
        // availableProcessors is including HT (for example "8" on a quad core)
        int processors = Runtime.getRuntime().availableProcessors();
        if (processors < 2) {
            processors = 2;
        } // minimum of two workers
        loop = new TickedEventLoop(EnvironmentConf.TICK_LENGTH, processors, null);

        resourceDB = new ResourceDB(loop);
        loop.addLoopEvent(resourceDB);

        resourceDB.addZip(new File("assets/gui.zip"));

        loop.setup();

        connectLoop = new ConnectLoop(serverUri, resourceDB, loop, nickname);

        state = STATE.NONE;
        if (!connectLoop.loop()) {
            log.log(Level.SEVERE, "Connection failed");
            NetworkedGame networkedGame = connectLoop.getNetworkedGame();
            AuthenticateResponse.ERROR authError = networkedGame.getAuthError();
            WS_CLOSE_STATUS closeStatus = networkedGame.getDisconnectCode();

            breakdown();

            if (authError != null) {
                String reason = networkedGame.getAuthErrorDesc();
                if (reason == null)
                    reason = "";

                JOptionPane.showMessageDialog(null,
                        "Unable to authenticate to " + uri + " (code:" + authError + ")\n\n" + reason, "Aphelion",
                        JOptionPane.ERROR_MESSAGE);
            } else if (closeStatus != null) {
                String reason = networkedGame.getDisconnectReason();
                if (reason == null)
                    reason = "";

                JOptionPane.showMessageDialog(null,
                        "Unable to connect to " + uri + " (code:" + closeStatus + ")\n\n" + reason, "Aphelion",
                        JOptionPane.ERROR_MESSAGE);
            }
            return;
        }

        loop.setClockSource(connectLoop.getSyncedClockSource());

        log.log(Level.INFO, "Connected");

        state = STATE.PLAYING;
        NetworkedGame netGame = connectLoop.getNetworkedGame();

        InitializeLoop initLoop = new InitializeLoop(connectLoop);
        if (initLoop.loop()) {
            GameLoop gameLoop = new GameLoop(initLoop);
            initLoop = null;
            gameLoop.loop();
            breakdown();

            if (gameLoop.isConnectionError()) {
                WS_CLOSE_STATUS code = netGame.getDisconnectCode();
                String reason = netGame.getDisconnectReason();
                if (reason == null)
                    reason = "";

                JOptionPane.showMessageDialog(null,
                        "Connection to the server suddenly dropped (" + uri + ") (code:" + code + ")\n\n" + reason,
                        "Aphelion", JOptionPane.ERROR_MESSAGE);
            }
        } else {
            log.log(Level.SEVERE, "Initialize failed");
            breakdown();
        }
    }

    public void breakdown() {
        state = STATE.QUITTING;

        if (loop != null) {
            loop.breakdown();
        }

        Display.destroy();
        Keyboard.destroy();

        if (connectLoop != null) {
            connectLoop.getConnection().stop();
        }
        if (serverThread != null) {
            serverThread.stopServer();
        }

        log.log(Level.INFO, "Breakdown completed");
    }

    public static void initGL() {
        int displayWidth = Display.getWidth();
        int displayHeight = Display.getHeight();

        glDisableAll();

        GL11.glViewport(0, 0, displayWidth, displayHeight);

        GL11.glMatrixMode(GL11.GL_PROJECTION); // Apply subsequent matrix operations to the projection matrix stack.
        GL11.glLoadIdentity();
        GL11.glOrtho(0, displayWidth, displayHeight, 0, -1, 1);

        GL11.glMatrixMode(GL11.GL_TEXTURE);
        GL11.glLoadIdentity();

        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glLoadIdentity();

        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
        AsyncTexture.unbind();

        // Enable alpha channels for images
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        GL11.glEnable(GL11.GL_BLEND);

        Graph.g.setDimensions(displayWidth, displayHeight);
        Graphics.setCurrent(Graph.g);
        Graph.g.setDrawMode(Graphics.MODE_NORMAL);
    }

    private static void glDisableAll() {
        GL11.glDisable(GL11.GL_BLEND);
        GL11.glDisable(GL11.GL_COLOR_LOGIC_OP);
        GL11.glDisable(GL11.GL_CULL_FACE);
        GL11.glDisable(GL11.GL_DEPTH_TEST);
        GL11.glDisable(GL11.GL_DITHER);
        GL11.glDisable(GL11.GL_LINE_SMOOTH);
        GL11.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
        GL11.glDisable(GL11.GL_POLYGON_OFFSET_LINE);
        GL11.glDisable(GL11.GL_POLYGON_OFFSET_POINT);
        GL11.glDisable(GL11.GL_POLYGON_SMOOTH);
        GL11.glDisable(GL11.GL_SCISSOR_TEST);
        GL11.glDisable(GL11.GL_STENCIL_TEST);
    }

    public static boolean wasResized() {
        boolean ret = Display.wasResized() || wasFullscreenChanged;
        wasFullscreenChanged = false;
        return ret;
    }

    private static boolean wasFullscreenChanged = false;
    private static int previousDesktopWidth = 1024;
    private static int previousDesktopHeight = 768;
    private static int previousDesktopX = 0;
    private static int previousDesktopY = 0;

    public static boolean setFullScreen(boolean fullscreen) {
        if (Display.isFullscreen() == fullscreen) {
            return true;
        }

        wasFullscreenChanged = true;
        if (fullscreen) {
            previousDesktopWidth = Display.getWidth();
            previousDesktopHeight = Display.getHeight();
            previousDesktopX = Display.getX();
            previousDesktopY = Display.getY();

            DisplayMode[] modes;
            try {
                modes = Display.getAvailableDisplayModes();
            } catch (LWJGLException ex) {
                log.log(Level.SEVERE, "Unable to determine available display modes!", ex);
                return false;
            }

            Arrays.sort(modes, new Comparator<DisplayMode>() {
                @Override
                public int compare(DisplayMode o1, DisplayMode o2) {
                    // more is better
                    int size1 = o1.getWidth() * o1.getHeight();
                    int size2 = o2.getWidth() * o2.getHeight();

                    if (o1.getBitsPerPixel() == o2.getBitsPerPixel()) {
                        if (size1 == size2) {
                            return -Integer.compare(o1.getFrequency(), o2.getFrequency());
                        }

                        return -Integer.compare(size1, size2);
                    }

                    return -Integer.compare(o1.getBitsPerPixel(), o2.getBitsPerPixel());
                }
            });

            for (DisplayMode mode : modes) {
                try {
                    Display.setDisplayModeAndFullscreen(mode);
                    return true;
                } catch (LWJGLException ex) {
                    log.log(Level.WARNING, "Unable to use display mode {0} ({1}). Trying next one",
                            new Object[] { mode, ex.getMessage() });
                }
            }

            log.log(Level.SEVERE, "Exhausted display modes, none work");
        } else {
            try {
                Display.setDisplayMode(new DisplayMode(previousDesktopWidth, previousDesktopHeight));
                Display.setLocation(previousDesktopX, previousDesktopY);
                return true;
            } catch (LWJGLException ex) {
                log.log(Level.SEVERE, "Unable to exit fullscreen!", ex);
                return false;
            }
        }

        return false;
    }

    public static boolean toggleFullScreen() {
        return setFullScreen(!Display.isFullscreen());
    }

    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

        Deadlock.start(true, null);

        Client client;
        client = new Client();

        try {
            String uri = null;
            String nickname = null;

            if (args.length >= 2) {
                uri = args[0];
                nickname = args[1];
            }

            if (args.length == 1) {
                uri = args[0];
            }

            if (uri != null) {
                if (uri.equalsIgnoreCase("null") || uri.equalsIgnoreCase("singleplayer")) {
                    uri = null;
                }
            }

            if (nickname == null) {
                nickname = "Player" + (int) (Math.random() * 1000000);
            }

            client.run(uri == null ? null : new URI(uri), nickname);
        } catch (Throwable ex) {
            client.breakdown();
            new ErrorDialog().setErrorText(ex);
            Deadlock.stop();
            throw ex;
        }
        Deadlock.stop();
    }
}