Java tutorial
/* * Copyright 2011 Benjamin Glatzel <benjamin.glatzel@me.com>. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.begla.blockmania.main; import com.github.begla.blockmania.gui.HUD; import com.github.begla.blockmania.rendering.FontManager; import com.github.begla.blockmania.rendering.ShaderManager; import com.github.begla.blockmania.rendering.VBOManager; import com.github.begla.blockmania.utilities.FastRandom; import com.github.begla.blockmania.world.World; import com.github.begla.blockmania.world.characters.Player; import javolution.util.FastList; import org.lwjgl.LWJGLException; import org.lwjgl.Sys; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.openal.AL; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; import org.lwjgl.util.vector.Vector3f; import org.newdawn.slick.SlickException; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import static org.lwjgl.opengl.GL11.*; /** * The heart and soul of Blockmania. * * @author Benjamin Glatzel <benjamin.glatzel@me.com> */ public final class Blockmania { private final ExecutorService _threadPool = Executors.newFixedThreadPool(Configuration.MAX_THREADS); /* ------ */ private static final int FRAME_SKIP_MAX_FRAMES = 5; private static final int TICKS_PER_SECOND = 60; private static final int SKIP_TICKS = 1000 / TICKS_PER_SECOND; /* ------- */ private long _lastLoopTime, _lastFpsTime; private int _fps; private final StringBuffer _consoleInput = new StringBuffer(); private boolean _pauseGame = false, _runGame = true, _saveWorldOnExit = true; /* ------- */ private double _averageFps; /* ------- */ private World _world; private HUD _hud; /* ------- */ private final FastRandom _rand = new FastRandom(); /* ------- */ private long _timerTicksPerSecond; /* ------- */ private static Blockmania _instance; /* ------- */ private final Logger _logger = Logger.getLogger("blockmania"); // Singleton public static Blockmania getInstance() { if (_instance == null) _instance = new Blockmania(); return _instance; } /** * Entry point of the application. * * @param args Arguments */ public static void main(String[] args) { initDefaultLogger(); Blockmania.getInstance().getLogger().log(Level.INFO, "Welcome to {0}!", Configuration.GAME_TITLE); try { loadNativeLibs(); } catch (Exception e) { Blockmania.getInstance().getLogger().log(Level.SEVERE, "Couldn't link static libraries. Sorry. " + e.toString(), e); } Blockmania blockmania = null; try { blockmania = Blockmania.getInstance(); blockmania.initDisplay(); blockmania.initControls(); blockmania.initGame(); blockmania.startGame(); } catch (LWJGLException e) { Blockmania.getInstance().getLogger().log(Level.SEVERE, "Failed to start game. I'm sorry. " + e.toString(), e); } catch (SlickException e) { Blockmania.getInstance().getLogger().log(Level.SEVERE, "Failed to start game. I'm sorry. " + e.toString(), e); } finally { if (blockmania != null) { blockmania.destroy(); } } System.exit(0); } private static void initDefaultLogger() { File dirPath = new File("logs"); if (!dirPath.exists()) { if (!dirPath.mkdirs()) { return; } } Blockmania.getInstance().addLogFileHandler("logs/blockmania.log", Level.INFO); } private static void loadNativeLibs() throws Exception { if (System.getProperty("os.name").equals("Mac OS X")) addLibraryPath("natives/macosx"); else if (System.getProperty("os.name").equals("Linux")) addLibraryPath("natives/linux"); else addLibraryPath("natives/windows"); } private static void addLibraryPath(String s) throws Exception { final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths"); usrPathsField.setAccessible(true); final String[] paths = (String[]) usrPathsField.get(null); for (String path : paths) { if (path.equals(s)) { return; } } final String[] newPaths = Arrays.copyOf(paths, paths.length + 1); newPaths[newPaths.length - 1] = s; usrPathsField.set(null, newPaths); } /** * Returns the system time in milliseconds. * * @return The system time in milliseconds. */ public long getTime() { if (_timerTicksPerSecond == 0) return 0; return (Sys.getTime() * 1000) / _timerTicksPerSecond; } /** * Init. the display. * * @throws LWJGLException */ public void initDisplay() throws LWJGLException { if (Configuration.FULLSCREEN) { Display.setDisplayMode(Display.getDesktopDisplayMode()); Display.setFullscreen(true); } else { Display.setDisplayMode(Configuration.DISPLAY_MODE); } Display.setTitle(Configuration.GAME_TITLE); Display.create(Configuration.PIXEL_FORMAT); } /** * Init. keyboard and mouse input. * * @throws LWJGLException */ public void initControls() throws LWJGLException { // Keyboard Keyboard.create(); Keyboard.enableRepeatEvents(true); // Mouse Mouse.setGrabbed(true); Mouse.create(); } /** * Clean up before exiting the application. */ public void destroy() { AL.destroy(); Mouse.destroy(); Keyboard.destroy(); Display.destroy(); } public void initGame() throws SlickException { _timerTicksPerSecond = Sys.getTimerResolution(); /* * Init. GUI. */ _hud = new HUD(); /* * Init. management classes. */ ShaderManager.getInstance(); VBOManager.getInstance(); FontManager.getInstance(); /* * Init. OpenGL */ resizeViewport(); setupOpenGL(); // Generate a world with a random seed value String worldSeed = Configuration.DEFAULT_SEED; if (worldSeed.length() == 0) { worldSeed = _rand.randomCharacterString(16); } initNewWorldAndPlayer("World1", worldSeed); } private void setupOpenGL() { glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glFogi(GL_FOG_MODE, GL_LINEAR); // Update the viewing distance double minDist = Math.min(Configuration.getSettingNumeric("V_DIST_X") * Configuration.CHUNK_DIMENSIONS.x, Configuration.getSettingNumeric("V_DIST_Z") * Configuration.CHUNK_DIMENSIONS.z); double viewingDistance = minDist / 2f; glFogf(GL_FOG_START, (float) (viewingDistance * 0.25)); glFogf(GL_FOG_END, (float) viewingDistance); GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST); glShadeModel(GL11.GL_SMOOTH); } /** * Renders the scene. */ private void render() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); // RENDER WORLD _world.render(); _hud.render(); } private void resizeViewport() { glViewport(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight()); } /** * Starts the render loop. */ public void startGame() { Blockmania.getInstance().getLogger().log(Level.INFO, "Starting Blockmania..."); _lastLoopTime = getTime(); double nextGameTick = getTime(); int loopCounter; /* * Blockmania game loop. */ while (_runGame && !Display.isCloseRequested()) { updateStatistics(); processKeyboardInput(); processMouseInput(); // Pause the game while the debug console is being shown loopCounter = 0; while (getTime() > nextGameTick && loopCounter < FRAME_SKIP_MAX_FRAMES) { if (!_pauseGame) { update(); } nextGameTick += SKIP_TICKS; loopCounter++; } render(); // Clear dirty flag and swap buffer Display.update(); } /* * Save the world and exit the application. */ if (_saveWorldOnExit) { _world.dispose(); } _threadPool.shutdown(); try { _threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { } Display.destroy(); } public void stopGame() { _runGame = false; } public void pauseGame() { Mouse.setGrabbed(false); _pauseGame = true; } public void unpauseGame() { _pauseGame = false; Mouse.setGrabbed(true); } /** * Executes updates. */ private void update() { _hud.update(); _world.update(); } /* * Process mouse input. */ private void processMouseInput() { while (Mouse.next()) { int button = Mouse.getEventButton(); _world.getPlayer().processMouseInput(button, Mouse.getEventButtonState()); } } /** * Processes keyboard input. */ private void processKeyboardInput() { while (Keyboard.next()) { int key = Keyboard.getEventKey(); if (key == Keyboard.KEY_ESCAPE && !Keyboard.isRepeatEvent() && Keyboard.getEventKeyState()) { toggleDebugConsole(); } if (key == Keyboard.KEY_F3 && !Keyboard.isRepeatEvent() && Keyboard.getEventKeyState()) { Configuration.setSetting("DEBUG", !Configuration.getSettingBoolean("DEBUG")); } if (_pauseGame) { if (!Keyboard.isRepeatEvent() && Keyboard.getEventKeyState()) { if (key == Keyboard.KEY_BACK) { int length = _consoleInput.length() - 1; if (length < 0) { length = 0; } _consoleInput.setLength(length); } else if (key == Keyboard.KEY_RETURN) { processConsoleString(); } char c = Keyboard.getEventCharacter(); if (c >= 'a' && c < 'z' + 1 || c >= '0' && c < '9' + 1 || c >= 'A' && c < 'A' + 1 || c == ' ' || c == '_' || c == '.' || c == '!' || c == '-') { _consoleInput.append(c); } } } else { // Pass input to the current player _world.getPlayer().processKeyboardInput(key, Keyboard.getEventKeyState(), Keyboard.isRepeatEvent()); } } } /** * Parses the console string and executes the command. */ private void processConsoleString() { boolean success = false; FastList<String> parsingResult = new FastList<String>(); String temp = ""; for (int i = 0; i < _consoleInput.length(); i++) { char c = _consoleInput.charAt(i); if (c != ' ') { temp = temp.concat(String.valueOf(c)); } if (c == ' ' || i == _consoleInput.length() - 1) { parsingResult.add(temp); temp = ""; } } // Try to parse the input try { if (parsingResult.get(0).equals("place")) { if (parsingResult.get(1).equals("tree")) { _world.getPlayer().plantTree(Integer.parseInt(parsingResult.get(2))); success = true; } else if (parsingResult.get(1).equals("block")) { _world.getPlayer().placeBlock(Byte.parseByte(parsingResult.get(2))); success = true; } } else if (parsingResult.get(0).equals("set")) { if (parsingResult.get(1).equals("time")) { _world.setTime(Float.parseFloat(parsingResult.get(2))); success = true; // Otherwise try lookup the given variable within the settings } else { Boolean bRes = Configuration.getSettingBoolean(parsingResult.get(1).toUpperCase()); if (bRes != null) { Configuration.setSetting(parsingResult.get(1).toUpperCase(), Boolean.parseBoolean(parsingResult.get(2))); success = true; } else { Double fRes = Configuration.getSettingNumeric(parsingResult.get(1).toUpperCase()); if (fRes != null) { Configuration.setSetting(parsingResult.get(1).toUpperCase(), Double.parseDouble(parsingResult.get(2))); success = true; } } } } else if (parsingResult.get(0).equals("respawn")) { _world.getPlayer().respawn(); success = true; } else if (parsingResult.get(0).equals("goto")) { int x = Integer.parseInt(parsingResult.get(1)); int y = Integer.parseInt(parsingResult.get(2)); int z = Integer.parseInt(parsingResult.get(3)); _world.getPlayer().setPosition(new Vector3f(x, y, z)); success = true; } else if (parsingResult.get(0).equals("exit")) { _saveWorldOnExit = true; _runGame = false; success = true; } else if (parsingResult.get(0).equals("exit!")) { _saveWorldOnExit = false; _runGame = false; success = true; } else if (parsingResult.get(0).equals("load")) { String worldSeed = _rand.randomCharacterString(16); if (parsingResult.size() > 1) { worldSeed = parsingResult.get(1); } initNewWorldAndPlayer(worldSeed, worldSeed); success = true; } else if (parsingResult.get(0).equals("set_spawn")) { _world.getPlayer().setSpawningPoint(); success = true; } } catch (Exception e) { Blockmania.getInstance().getLogger().log(Level.INFO, e.getMessage()); } if (success) { setupOpenGL(); Blockmania.getInstance().getLogger().log(Level.INFO, "Console command \"{0}\" accepted.", _consoleInput); } else { Blockmania.getInstance().getLogger().log(Level.WARNING, "Console command \"{0}\" is invalid.", _consoleInput); } toggleDebugConsole(); } /** * Disables/enables the debug console. */ private void toggleDebugConsole() { if (!_pauseGame) { pauseGame(); _consoleInput.setLength(0); } else { unpauseGame(); } } /** * Prepares a new world with a given name and seed value. * * @param title Title of the world * @param seed Seed value used for the generators */ private void initNewWorldAndPlayer(String title, String seed) { Blockmania.getInstance().getLogger().log(Level.INFO, "Creating new World with seed \"{0}\"", seed); // Get rid of the old world if (_world != null) { _world.dispose(); } // Init. a new world _world = new World(title, seed); _world.setPlayer(new Player(_world)); // Reset the delta value _lastLoopTime = getTime(); } /** * Updates the game statistics like FPS and memory usage. */ private void updateStatistics() { // Measure a delta value and the frames per second long delta = getTime() - _lastLoopTime; _lastLoopTime = getTime(); _lastFpsTime += delta; _fps++; // Update the FPS and calculate the average if (_lastFpsTime >= 1000) { _lastFpsTime = 0; _averageFps += _fps; _averageFps /= 2; _fps = 0; } } public void addLogFileHandler(String s, Level logLevel) { try { FileHandler fh = new FileHandler(s, true); fh.setLevel(logLevel); fh.setFormatter(new SimpleFormatter()); _logger.addHandler(fh); } catch (IOException ex) { _logger.log(Level.WARNING, ex.toString(), ex); } } public Logger getLogger() { return _logger; } public boolean isGamePaused() { return _pauseGame; } public double getAverageFps() { return _averageFps; } public World getActiveWorld() { return _world; } public StringBuffer getConsoleInput() { return _consoleInput; } public ExecutorService getThreadPool() { return _threadPool; } }