| /** | |
| * Copyright (c) 2010-2011, Vincent Vollers and Christopher J. Kucera | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * * Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * * Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * * Neither the name of the Minecraft X-Ray team nor the | |
| * names of its contributors may be used to endorse or promote products | |
| * derived from this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| * DISCLAIMED. IN NO EVENT SHALL VINCENT VOLLERS OR CJ KUCERA BE LIABLE FOR ANY | |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| package com.plusminus.craft; | |
| import java.awt.AlphaComposite; | |
| import java.awt.BasicStroke; | |
| import java.awt.Color; | |
| import java.awt.Font; | |
| import java.awt.Graphics2D; | |
| import java.awt.Image; | |
| import java.awt.image.BufferedImage; | |
| import java.awt.image.DataBufferByte; | |
| import java.awt.Rectangle; | |
| import java.awt.geom.Rectangle2D; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.io.FileInputStream; | |
| import java.io.FileOutputStream; | |
| import java.nio.ByteBuffer; | |
| import java.nio.FloatBuffer; | |
| import java.util.HashMap; | |
| import java.util.ArrayList; | |
| import java.util.LinkedList; | |
| import java.util.Properties; | |
| import java.util.Enumeration; | |
| import java.util.Vector; | |
| import java.util.Collections; | |
| import javax.imageio.ImageIO; | |
| import javax.swing.JOptionPane; | |
| import javax.swing.JFileChooser; | |
| import org.lwjgl.Sys; | |
| import org.lwjgl.opengl.Display; | |
| import org.lwjgl.opengl.DisplayMode; | |
| import org.lwjgl.opengl.GL11; | |
| import org.lwjgl.util.glu.*; | |
| import org.lwjgl.input.Keyboard; | |
| import org.lwjgl.input.Mouse; | |
| import com.plusminus.craft.WorldInfo; | |
| import static com.plusminus.craft.MineCraftConstants.*; | |
| public class XRay | |
| { | |
| /** | |
| * Private class to provide a sorted properties list in our config file. | |
| * Taken from http://www.rgagnon.com/javadetails/java-0614.html | |
| */ | |
| private class SortedProperties extends Properties | |
| { | |
| // Added at the behest of Eclipse (or, well, presumably Java itself) | |
| private static final long serialVersionUID = 2578311914423692774L; | |
| /** | |
| * Overrides, called by the store method. | |
| */ | |
| @SuppressWarnings({ "unchecked", "rawtypes" }) | |
| public synchronized Enumeration<Object> keys() | |
| { | |
| Enumeration keysEnum = super.keys(); | |
| Vector keyList = new Vector(); | |
| while (keysEnum.hasMoreElements()) | |
| { | |
| keyList.add(keysEnum.nextElement()); | |
| } | |
| Collections.sort(keyList); | |
| return keyList.elements(); | |
| } | |
| } | |
| // number of chunks around the camera which are visible (Square) | |
| private int visible_chunk_range = 5; | |
| private static final int[] CHUNK_RANGES_KEYS = new int[6]; | |
| private static final int[] CHUNK_RANGES = new int[] { 3, 4, 5, 6, 7, 8 }; | |
| private int currentChunkRange = 4; | |
| // highlight distance | |
| private static final int[] HIGHLIGHT_RANGES_KEYS = new int[7]; | |
| private static final int[] HIGHLIGHT_RANGES = new int[] { 2, 3, 4, 5, 6, 7, 8 }; | |
| private int currentHighlightDistance = 1; | |
| // ore highlight vars | |
| private static BLOCK[] HIGHLIGHT_ORES = new BLOCK[preferred_highlight_ores.length]; | |
| private static final int[] HIGHLIGHT_ORE_KEYS = new int[preferred_highlight_ores.length]; | |
| // By default we'll keep 20x20 chunks in our cache, which should hopefully let | |
| // us stay ahead of the camera | |
| // TODO: keep this at 8, or back up to 10? | |
| private final int loadChunkRange = 8; | |
| // set to true when the program is finished | |
| private boolean done = false; | |
| // are we full screen | |
| private boolean fullscreen = false; | |
| // window title | |
| private final String app_version = "2.7 Maintenance Branch 8"; | |
| private final String app_name = "Minecraft X-Ray"; | |
| private final String windowTitle = app_name + " " + app_version; | |
| // Minimap size - I did try increasing this but there were some performance | |
| // issues | |
| private final int minimap_dim = 2048; | |
| private final float minimap_dim_f = (float) minimap_dim; | |
| private final int minimap_dim_h = minimap_dim / 2; | |
| private final float minimap_dim_h_f = (float) minimap_dim_h; | |
| private boolean minimap_needs_updating = false; | |
| // current display mode | |
| private DisplayMode displayMode; | |
| // last system time in the main loop (to calculate delta for camera movement) | |
| private long lastTime; | |
| // our camera | |
| private FirstPersonCameraController camera; | |
| private boolean camera_lock = false; | |
| // the current mouseX and mouseY on the screen | |
| private int mouseX; | |
| private int mouseY; | |
| // the sprite sheet for all textures | |
| public Texture minecraftTexture; | |
| public Texture paintingTexture; | |
| public Texture portalTexture; | |
| public Texture loadingTextTexture; | |
| // the textures used by the minimap | |
| private Texture minimapTexture; | |
| private Texture minimapArrowTexture; | |
| private Graphics2D minimapGraphics; | |
| // Whether or not we're showing bedrock/water/explored areas | |
| private boolean render_bedrock = false; | |
| private boolean render_water = true; | |
| private boolean highlight_explored = false; | |
| // the minecraft level we are exploring | |
| private MinecraftLevel level; | |
| // the current block (universal coordinate) where the camera is hovering on | |
| private int levelBlockX, levelBlockZ; | |
| // the current and previous chunk coordinates where the camera is hovering on | |
| private int currentLevelX, currentLevelZ; | |
| // we render to a display list and use that later for quick drawing, this is the index | |
| @SuppressWarnings("unused") | |
| private int worldDisplayListNum; | |
| @SuppressWarnings("unused") | |
| private int visibleOresListNum; | |
| // wheter we need to reload the world | |
| private boolean needToReloadWorld = false; | |
| // the width and height of the current screen resolution | |
| private int screenWidth, screenHeight; | |
| // the current camera position | |
| private int currentCameraPosX; | |
| private int currentCameraPosZ; | |
| // wheter we show the big map or the mini map | |
| private boolean mapBig = false; | |
| // wheter we are done with loading the map data (just for the mini map really) | |
| private boolean map_load_started = false; | |
| // the available world numbers | |
| private ArrayList<WorldInfo> availableWorlds; | |
| private int selectedWorld; | |
| // the world chunks we still need to load | |
| private LinkedList<Block> mapChunksToLoad; | |
| // the current (selected) world number | |
| private WorldInfo world = null; | |
| // the currently pressed key | |
| private int keyPressed = -1; | |
| // the current fps we are 'doing' | |
| private int fps; | |
| // the laste time fps was updated | |
| private long lastFpsTime = -1; | |
| // the number of frames since the last fps update | |
| private int framesSinceLastFps; | |
| // the fps display texture | |
| private Texture fpsTexture; | |
| // far too many fps calculation variables (copied this from another project) | |
| public long previousTime; | |
| public long timeDelta; | |
| private boolean updateFPSText; | |
| private long time; | |
| private boolean[] mineralToggle; | |
| private Texture[] mineralToggleTextures; | |
| // lighting on or of (its actually fog, but hey...) | |
| private boolean lightMode = true; | |
| // highlight the ores by making them blink | |
| private boolean highlightOres = true; | |
| // level info texture | |
| private boolean levelInfoToggle = false; | |
| private Texture levelInfoTexture; | |
| private boolean renderDetailsToggle = true; | |
| private Texture renderDetailsTexture; | |
| private int renderDetails_w = 160; | |
| private int cur_renderDetails_h; | |
| private int levelInfoTexture_h = 144; | |
| // light level | |
| private int[] lightLevelEnd = new int[] { 30, 50, 70, 100, 130 }; | |
| private int[] lightLevelStart = new int[] { 0, 20, 30, 40, 60 }; | |
| private int currentLightLevel = 2; | |
| // vars to keep track of our current chunk coordinates | |
| private int cur_chunk_x = 0; | |
| private int cur_chunk_z = 0; | |
| private boolean initial_load_done = false; | |
| private boolean initial_load_queued = false; | |
| // vars to keep track of how much the camera has moved since our last | |
| // minimap trim. | |
| private int total_dX = 0; | |
| private int total_dZ = 0; | |
| private int minimap_trim_chunks = 10; | |
| private int minimap_trim_chunk_distance = 64; | |
| // How long are we allowed to spend loading chunks before we update? | |
| private long max_chunkload_time = Sys.getTimerResolution() / 10; // a tenth of a second | |
| // The current camera position that we're at | |
| private CameraPreset currentPosition; | |
| private String cameraTextOverride = null; | |
| private HashMap<KEY_ACTIONS, Integer> key_mapping; | |
| private SortedProperties xray_properties; | |
| // lets start with the program | |
| public static void main(String args[]) | |
| { | |
| new XRay().run(); | |
| } | |
| // go | |
| public void run() | |
| { | |
| try | |
| { | |
| // check whether we can access minecraft | |
| // and if we have worlds to load | |
| checkMinecraftFiles(); | |
| // Load our preferences (this includes key mappings) | |
| setPreferenceDefaults(); | |
| loadPreferences(); | |
| // prompt for the resolution and initialize the window | |
| createWindow(); | |
| // Save any prefs which may have changed | |
| savePreferences(); | |
| // basic opengl initialization | |
| initGL(); | |
| // init our program | |
| initialize(); | |
| // And now load our world | |
| this.setMinecraftWorld(availableWorlds.get(this.selectedWorld)); | |
| this.triggerChunkLoads(); | |
| // Render details | |
| updateRenderDetails(); | |
| // main loop | |
| while (!done) | |
| { | |
| long time = Sys.getTime(); | |
| float timeDelta = (time - lastTime) / 1000.0f; | |
| lastTime = time; | |
| // handle input given the timedelta (for mouse control) | |
| handleInput(timeDelta); | |
| // Load chunks if needed | |
| if (mapChunksToLoad != null) | |
| { | |
| loadPendingChunks(); | |
| } | |
| // render whatever we need to render | |
| render(timeDelta); | |
| // update our minimap if we need to (new chunks loaded, etc) | |
| if (minimap_needs_updating) | |
| { | |
| minimapTexture.update(); | |
| minimap_needs_updating = false; | |
| } | |
| // Sleep a bit if we're not visible, to save on CPU | |
| // This is especially important when isVisible() is false, because | |
| // Display.update() does NOT vSync in that case. | |
| if (!Display.isVisible()) | |
| { | |
| Thread.sleep(100); | |
| } | |
| else if (!Display.isActive()) | |
| { | |
| Thread.sleep(33); | |
| } | |
| // Push to screen | |
| Display.update(); | |
| } | |
| // cleanup | |
| cleanup(); | |
| } | |
| catch (Exception e) | |
| { | |
| // bah some error happened | |
| e.printStackTrace(); | |
| System.exit(0); | |
| } | |
| } | |
| /** | |
| * Loads our preferences. This also sets our default keybindings if they're | |
| * not overridden somewhere. | |
| */ | |
| public void loadPreferences() | |
| { | |
| xray_properties = new SortedProperties(); | |
| // First load our defaults into the prefs object | |
| for (KEY_ACTIONS action : KEY_ACTIONS.values()) | |
| { | |
| xray_properties.setProperty("KEY_" + action.toString(), Keyboard.getKeyName(this.key_mapping.get(action))); | |
| } | |
| // Here's where we would load from our prefs file | |
| File prefs = MineCraftEnvironment.getXrayConfigFile(); | |
| if (prefs.exists() && prefs.canRead()) | |
| { | |
| try | |
| { | |
| xray_properties.load(new FileInputStream(prefs)); | |
| } | |
| catch (IOException e) | |
| { | |
| // Just report and continue | |
| System.out.println("Could not load configuration file: " + e.toString()); | |
| } | |
| } | |
| // Loop through the key mappings that we just loaded | |
| int newkey; | |
| String prefskey; | |
| for (KEY_ACTIONS action : KEY_ACTIONS.values()) | |
| { | |
| prefskey = xray_properties.getProperty("KEY_" + action.toString()); | |
| if (prefskey.equalsIgnoreCase("none")) | |
| { | |
| // If the user actually specified "NONE" in the config file, | |
| // unbind the key | |
| newkey = Keyboard.KEY_NONE; | |
| } | |
| else | |
| { | |
| newkey = Keyboard.getKeyIndex(prefskey); | |
| if (newkey == Keyboard.KEY_NONE) | |
| { | |
| // TODO: Should output something more visible to the user | |
| System.out.println("Warning: key '" + prefskey + "' for action " + action + " in the config file is unknown. Default key assigned."); | |
| continue; | |
| } | |
| } | |
| this.key_mapping.put(action, newkey); | |
| } | |
| // Populate our key ranges | |
| int i; | |
| for (i = 0; i < CHUNK_RANGES.length; i++) | |
| { | |
| CHUNK_RANGES_KEYS[i] = this.key_mapping.get(KEY_ACTIONS.valueOf("CHUNK_RANGE_" + (i + 1))); | |
| } | |
| for (i = 0; i < HIGHLIGHT_RANGES.length; i++) | |
| { | |
| HIGHLIGHT_RANGES_KEYS[i] = this.key_mapping.get(KEY_ACTIONS.valueOf("HIGHLIGHT_RANGE_" + (i + 1))); | |
| } | |
| for (i = 0; i < HIGHLIGHT_ORES.length; i++) | |
| { | |
| HIGHLIGHT_ORE_KEYS[i] = this.key_mapping.get(KEY_ACTIONS.valueOf("TOGGLE_ORE_" + (i + 1))); | |
| } | |
| // Populate our list of ores to highlight | |
| String prefs_highlight; | |
| String prefs_highlight_key; | |
| for (i = 0; i < preferred_highlight_ores.length; i++) | |
| { | |
| prefs_highlight_key = "HIGHLIGHT_" + (i + 1); | |
| prefs_highlight = xray_properties.getProperty(prefs_highlight_key); | |
| try | |
| { | |
| HIGHLIGHT_ORES[i] = BLOCK.valueOf(prefs_highlight); | |
| } | |
| catch (Exception e) | |
| { | |
| // no worries, just populate with our default | |
| } | |
| xray_properties.put(prefs_highlight_key, HIGHLIGHT_ORES[i].toString()); | |
| } | |
| // Save the file immediately, in case we picked up new defaults which weren't present previously | |
| this.savePreferences(); | |
| } | |
| /** | |
| * Saves our preferences out | |
| */ | |
| public void savePreferences() | |
| { | |
| File prefs = MineCraftEnvironment.getXrayConfigFile(); | |
| try | |
| { | |
| xray_properties.store(new FileOutputStream(prefs), | |
| "Feel free to edit. Use \"NONE\" to disable an action. Keys taken from http://www.lwjgl.org/javadoc/constant-values.html#org.lwjgl.input.Keyboard.KEY_1"); | |
| } | |
| catch (IOException e) | |
| { | |
| // Just report on the console and move on | |
| System.out.println("Could not save preferences to file: " + e.toString()); | |
| } | |
| } | |
| /** | |
| * Sets our default preferences | |
| */ | |
| public void setPreferenceDefaults() | |
| { | |
| // First do the default key mappings | |
| key_mapping = new HashMap<KEY_ACTIONS, Integer>(); | |
| for (KEY_ACTIONS action : KEY_ACTIONS.values()) | |
| { | |
| key_mapping.put(action, action.def_key); | |
| } | |
| // Then populate our highlight blocks | |
| for (int i = 0; i < preferred_highlight_ores.length; i++) | |
| { | |
| XRay.HIGHLIGHT_ORES[i] = preferred_highlight_ores[i]; | |
| } | |
| } | |
| /** | |
| * Loads any pending chunks, but won't exceed max_chunkload_time timer ticks | |
| * (unless we're doing the initial load). | |
| */ | |
| public void loadPendingChunks() | |
| { | |
| Block b; | |
| long time = Sys.getTime(); | |
| int total = 0; | |
| int counter = 0; | |
| if (!initial_load_done) | |
| { | |
| total = mapChunksToLoad.size(); | |
| setOrthoOn(); | |
| GL11.glDisable(GL11.GL_BLEND); | |
| GL11.glDisable(GL11.GL_TEXTURE_2D); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); | |
| GL11.glLineWidth(20); | |
| BufferedImage i = loadingTextTexture.getImage(); | |
| Graphics2D g = i.createGraphics(); | |
| g.setColor(new Color(0f, 0f, 0f, 0f)); | |
| g.setComposite(AlphaComposite.Src); | |
| g.fillRect(0, 0, i.getWidth(), i.getHeight()); | |
| String statusmessage; | |
| if (this.cameraTextOverride == null) | |
| { | |
| statusmessage = "Moving camera to " + this.currentPosition.name; | |
| } | |
| else | |
| { | |
| statusmessage = "Moving camera to " + this.cameraTextOverride; | |
| this.cameraTextOverride = null; | |
| } | |
| Rectangle2D bounds = HEADERFONT.getStringBounds(statusmessage, g.getFontRenderContext()); | |
| g.setFont(HEADERFONT); | |
| g.setColor(Color.white); | |
| g.drawString(statusmessage, (1024 / 2) - ((float) bounds.getWidth() / 2), 40f); | |
| loadingTextTexture.update(); | |
| } | |
| // There's various cases where parts of our crosshairs may be covered over by | |
| // other blocks, or bits of the crosshairs left on the map when wrapping, etc. | |
| // Whatever. | |
| boolean got_spawn_chunk = false; | |
| boolean got_playerpos_chunk = false; | |
| CameraPreset spawn = level.getSpawnPoint(); | |
| CameraPreset playerpos = level.getPlayerPosition(); | |
| Chunk c; | |
| while (!mapChunksToLoad.isEmpty()) | |
| { | |
| // Load and draw the chunk | |
| b = (Block) mapChunksToLoad.pop(); | |
| // System.out.println("Loading chunk " + b.x + "," + b.z); | |
| // There may be some circumstances where a chunk we're going to load is already loaded. | |
| // Mostly while moving diagonally, I think. I'm actually not convinced that it's worth | |
| // checking for, as it doesn't happen TOO often. | |
| c = level.getChunk(b.x, b.z); | |
| if (c != null) | |
| { | |
| if (c.x == b.x && c.z == b.z) | |
| { | |
| continue; | |
| } | |
| } | |
| level.loadChunk(b.x, b.z); | |
| drawChunkToMap(b.x, b.z); | |
| if (spawn.block.cx == b.x && spawn.block.cz == b.z) | |
| { | |
| got_spawn_chunk = true; | |
| } | |
| if (playerpos.block.cx == b.x && playerpos.block.cz == b.z) | |
| { | |
| got_playerpos_chunk = true; | |
| } | |
| // Make sure we update the minimap | |
| minimap_needs_updating = true; | |
| // Draw a progress bar if we're doing the initial load | |
| if (!initial_load_done) | |
| { | |
| counter++; | |
| if (counter % 5 == 0) | |
| { | |
| float progress = ((float) counter / (float) total); | |
| float bx = 100; | |
| float ex = screenWidth - 100; | |
| float by = (screenHeight / 2.0f) - 50; | |
| float ey = (screenHeight / 2.0f) + 50; | |
| float px = ((ex - bx) * progress) + bx; | |
| // progress bar outer box | |
| GL11.glBegin(GL11.GL_LINE_LOOP); | |
| GL11.glVertex2f(bx, by); | |
| GL11.glVertex2f(ex, by); | |
| GL11.glVertex2f(ex, ey); | |
| GL11.glVertex2f(bx, ey); | |
| GL11.glEnd(); | |
| // progress bar 'progress' | |
| GL11.glBegin(GL11.GL_TRIANGLE_STRIP); | |
| GL11.glVertex2f(bx, by); | |
| GL11.glVertex2f(px, by); | |
| GL11.glVertex2f(bx, ey); | |
| GL11.glVertex2f(px, ey); | |
| GL11.glEnd(); | |
| // Draw our message | |
| GL11.glEnable(GL11.GL_BLEND); | |
| GL11.glEnable(GL11.GL_TEXTURE_2D); | |
| SpriteTool.drawSpriteAbsoluteXY(loadingTextTexture, 0f, by - 100); | |
| GL11.glDisable(GL11.GL_BLEND); | |
| GL11.glDisable(GL11.GL_TEXTURE_2D); | |
| Display.update(); | |
| } | |
| } | |
| else | |
| { | |
| // Otherwise (if our initial load is done), mark any existing adjacent chunks | |
| // as dirty so that they re-render. This is needed so that we don't get gaps | |
| // in our terrain because the adjacent chunks weren't ready yet. | |
| level.markChunkAsDirty(b.x + 1, b.z); | |
| level.markChunkAsDirty(b.x - 1, b.z); | |
| level.markChunkAsDirty(b.x, b.z + 1); | |
| level.markChunkAsDirty(b.x, b.z - 1); | |
| } | |
| // If we've taken too long, break out so the GUI can update | |
| if (initial_load_done && Sys.getTime() - time > max_chunkload_time) | |
| { | |
| break; | |
| } | |
| } | |
| if (got_spawn_chunk) | |
| { | |
| drawSpawnMarkerToMinimap(); | |
| } | |
| if (got_playerpos_chunk) | |
| { | |
| drawPlayerposMarkerToMinimap(); | |
| } | |
| if (!initial_load_done) | |
| { | |
| GL11.glEnable(GL11.GL_BLEND); | |
| GL11.glEnable(GL11.GL_TEXTURE_2D); | |
| setOrthoOff(); | |
| } | |
| initial_load_done = true; | |
| } | |
| public void incLightLevel() | |
| { | |
| this.currentLightLevel++; | |
| if (this.currentLightLevel >= this.lightLevelStart.length) | |
| { | |
| this.currentLightLevel = this.lightLevelStart.length - 1; | |
| } | |
| } | |
| public void decLightLevel() | |
| { | |
| this.currentLightLevel--; | |
| if (this.currentLightLevel <= 0) | |
| { | |
| this.currentLightLevel = 0; | |
| } | |
| } | |
| public void setLightLevel() | |
| { | |
| this.setLightLevel(0); | |
| } | |
| public void setLightLevel(int diff) | |
| { | |
| int min = this.lightLevelStart[this.currentLightLevel]; | |
| int max = this.lightLevelEnd[this.currentLightLevel]; | |
| min = min + diff; | |
| max = max + diff; | |
| if (min <= 0) | |
| { | |
| min = 0; | |
| } | |
| if (max <= 0) | |
| { | |
| max = 0; | |
| } | |
| GL11.glFogf(GL11.GL_FOG_START, min); | |
| GL11.glFogf(GL11.GL_FOG_END, max); | |
| } | |
| /*** | |
| * Initialize the basic openGL environment | |
| */ | |
| private void initGL() | |
| { | |
| GL11.glEnable(GL11.GL_TEXTURE_2D); // Enable Texture Mapping | |
| GL11.glShadeModel(GL11.GL_FLAT); // Disable Smooth Shading | |
| GL11.glClearColor(0.0f, 0.3f, 1.0f, 0.3f); // Blue Background | |
| GL11.glClearDepth(1.0); // Depth Buffer Setup | |
| GL11.glEnable(GL11.GL_DEPTH_TEST); // Enables Depth Testing | |
| GL11.glDepthFunc(GL11.GL_LEQUAL); // The Type Of Depth Testing To Do | |
| // GL11.glDepthFunc(GL11.GL_ALWAYS); | |
| GL11.glEnable(GL11.GL_BLEND); | |
| GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); | |
| // GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE); | |
| GL11.glMatrixMode(GL11.GL_PROJECTION); // Select The Projection Matrix | |
| GL11.glLoadIdentity(); // Reset The Projection Matrix | |
| // Calculate The Aspect Ratio Of The Window | |
| GLU.gluPerspective(90.0f, (float) displayMode.getWidth() / (float) displayMode.getHeight(), 0.1f, 400.0f); | |
| GL11.glMatrixMode(GL11.GL_MODELVIEW); // Select The Modelview Matrix | |
| // Really Nice Perspective Calculations | |
| GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST); | |
| GL11.glDisable(GL11.GL_FOG); | |
| GL11.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR); | |
| float[] color = new float[] { 0.0f, 0.3f, 1.0f, 0.3f }; | |
| ByteBuffer colorBytes = ByteBuffer.allocateDirect(64); | |
| FloatBuffer colorBuffer = colorBytes.asFloatBuffer(); | |
| colorBuffer.rewind(); | |
| colorBuffer.put(color); | |
| colorBuffer.rewind(); | |
| GL11.glFog(GL11.GL_FOG_COLOR, colorBytes.asFloatBuffer()); | |
| GL11.glFogf(GL11.GL_FOG_DENSITY, 0.3f); | |
| GL11.glHint(GL11.GL_FOG_HINT, GL11.GL_NICEST); | |
| setLightLevel(); | |
| } | |
| /*** | |
| * Load textures init precalc tables determine available worlds init misc | |
| * variables | |
| */ | |
| private void initialize() | |
| { | |
| // init the precalc tables | |
| mineralToggle = new boolean[HIGHLIGHT_ORES.length]; | |
| mineralToggleTextures = new Texture[HIGHLIGHT_ORES.length]; | |
| // world display list | |
| worldDisplayListNum = GL11.glGenLists(1); | |
| visibleOresListNum = GL11.glGenLists(1); | |
| // camera | |
| camera = new FirstPersonCameraController(0, 0, 0); | |
| // textures | |
| try | |
| { | |
| // Note that in order to avoid weird texture-resize fuzziness, these textures | |
| // should have dimensions which are powers of 2 | |
| minimapTexture = TextureTool.allocateTexture(minimap_dim, minimap_dim); | |
| minimapGraphics = minimapTexture.getImage().createGraphics(); | |
| minimapArrowTexture = TextureTool.allocateTexture(32, 32); | |
| fpsTexture = TextureTool.allocateTexture(128, 32); | |
| levelInfoTexture = TextureTool.allocateTexture(128, 256); | |
| loadingTextTexture = TextureTool.allocateTexture(1024, 64); | |
| renderDetailsTexture = TextureTool.allocateTexture(256, 256); | |
| createMinimapSprites(); | |
| // minecraft textures | |
| BufferedImage minecraftTextureImage = MineCraftEnvironment.getMinecraftTexture(); | |
| minecraftTexture = TextureTool.allocateTexture(minecraftTextureImage, GL11.GL_NEAREST); | |
| minecraftTexture.update(); | |
| // painting textures | |
| BufferedImage minecraftPaintingImage = MineCraftEnvironment.getMinecraftPaintings(); | |
| paintingTexture = TextureTool.allocateTexture(minecraftPaintingImage, GL11.GL_NEAREST); | |
| paintingTexture.update(); | |
| // Nether portal texture to use for drawing those, since there's no | |
| // actual texture for it | |
| portalTexture = TextureTool.allocateTexture(16, 16); | |
| BufferedImage bi = portalTexture.getImage(); | |
| Graphics2D pg = bi.createGraphics(); | |
| pg.setColor(new Color(.839f, .203f, .952f, .4f)); | |
| pg.fill(new Rectangle(0, 0, 16, 16)); | |
| pg.drawImage(bi, null, 0, 0); | |
| portalTexture.update(); | |
| // mineral textures | |
| for (int i = 0; i < HIGHLIGHT_ORES.length; i++) | |
| { | |
| mineralToggleTextures[i] = TextureTool.allocateTexture(128, 32); | |
| Graphics2D g = mineralToggleTextures[i].getImage().createGraphics(); | |
| g.setFont(ARIALFONT); | |
| g.setColor(Color.white); | |
| g.drawString("[F" + (i + 1) + "] " + HIGHLIGHT_ORES[i].name, 10, 16); | |
| mineralToggleTextures[i].update(); | |
| } | |
| } | |
| catch (IOException e1) | |
| { | |
| // TODO Auto-generated catch block | |
| e1.printStackTrace(); | |
| } | |
| // level data | |
| levelBlockX = Integer.MIN_VALUE; | |
| levelBlockZ = Integer.MIN_VALUE; | |
| // set mouse grabbed so we can get x/y coordinates | |
| Mouse.setGrabbed(true); | |
| } | |
| private BufferedImage resizeImage(Image baseImage, int newWidth, int newHeight) | |
| { | |
| BufferedImage newImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_4BYTE_ABGR); | |
| Graphics2D g = newImage.createGraphics(); | |
| g.drawImage(baseImage, 0, 0, newWidth, newHeight, null); | |
| return newImage; | |
| } | |
| private byte[] convertIcon(byte[] icon) | |
| { | |
| byte[] newIcon = new byte[icon.length]; | |
| for (int i = 0; i < newIcon.length; i += 4) | |
| { | |
| newIcon[i + 3] = icon[i + 0]; | |
| newIcon[i + 2] = icon[i + 1]; | |
| newIcon[i + 1] = icon[i + 2]; | |
| newIcon[i + 0] = icon[i + 3]; | |
| } | |
| return newIcon; | |
| } | |
| /*** | |
| * Creates the window and initializes the lwjgl display object | |
| * | |
| * @throws Exception | |
| */ | |
| private void createWindow() throws Exception | |
| { | |
| // set icon buffers | |
| // stupid conversions needed | |
| File iconFile = new File("xray_icon.png"); | |
| ByteBuffer[] icons = null; | |
| if (iconFile.exists() || iconFile.canRead()) | |
| { | |
| BufferedImage iconTexture128 = ImageIO.read(iconFile); | |
| iconTexture128 = resizeImage(iconTexture128, 128, 128); // just to be sure all icons are the same imagetype | |
| BufferedImage iconTexture64 = resizeImage(iconTexture128, 64, 64); | |
| BufferedImage iconTexture32 = resizeImage(iconTexture128, 32, 32); | |
| BufferedImage iconTexture16 = resizeImage(iconTexture128, 16, 16); | |
| byte[] iconBuffer128d = ((DataBufferByte) iconTexture128.getRaster().getDataBuffer()).getData(); | |
| byte[] iconBuffer64d = ((DataBufferByte) iconTexture64.getRaster().getDataBuffer()).getData(); | |
| byte[] iconBuffer32d = ((DataBufferByte) iconTexture32.getRaster().getDataBuffer()).getData(); | |
| byte[] iconBuffer16d = ((DataBufferByte) iconTexture16.getRaster().getDataBuffer()).getData(); | |
| iconBuffer128d = convertIcon(iconBuffer128d); // LWJGL (opengl?) needs RGBA ... imagetype available is ABGR | |
| iconBuffer64d = convertIcon(iconBuffer64d); | |
| iconBuffer32d = convertIcon(iconBuffer32d); | |
| iconBuffer16d = convertIcon(iconBuffer16d); | |
| ByteBuffer iconBuffer128 = ByteBuffer.wrap(iconBuffer128d); | |
| ByteBuffer iconBuffer64 = ByteBuffer.wrap(iconBuffer64d); | |
| ByteBuffer iconBuffer32 = ByteBuffer.wrap(iconBuffer32d); | |
| ByteBuffer iconBuffer16 = ByteBuffer.wrap(iconBuffer16d); | |
| iconBuffer128.rewind(); | |
| iconBuffer64.rewind(); | |
| iconBuffer32.rewind(); | |
| iconBuffer16.rewind(); | |
| icons = new ByteBuffer[] { iconBuffer128, iconBuffer64, iconBuffer32, iconBuffer16 }; | |
| ResolutionDialog.iconImage = iconTexture128; | |
| } | |
| // We loop on this dialog "forever" because | |
| while (true) | |
| { | |
| if (ResolutionDialog.presentDialog(windowTitle, availableWorlds, (Properties) xray_properties) == ResolutionDialog.DIALOG_BUTTON_EXIT) | |
| { | |
| System.exit(0); | |
| } | |
| // Mark which world to load (which will happen later during initialize() | |
| this.selectedWorld = ResolutionDialog.selectedWorld; | |
| // The last option will always be "Other..." If that's been chosen, open a chooser dialog. | |
| if (this.selectedWorld == availableWorlds.size() - 1) | |
| { | |
| JFileChooser chooser = new JFileChooser(); | |
| chooser.setFileHidingEnabled(false); | |
| chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); | |
| if (xray_properties.getProperty("LAST_WORLD") != null) | |
| { | |
| chooser.setCurrentDirectory(new File(xray_properties.getProperty("LAST_WORLD"))); | |
| } | |
| else | |
| { | |
| chooser.setCurrentDirectory(new File(".")); | |
| } | |
| chooser.setDialogTitle("Select a Minecraft World Directory"); | |
| if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) | |
| { | |
| WorldInfo customWorld = availableWorlds.get(this.selectedWorld); | |
| customWorld.setBasePath(chooser.getSelectedFile().getCanonicalPath()); | |
| File leveldat = customWorld.getLevelDatFile(); | |
| if (leveldat.exists() && leveldat.canRead()) | |
| { | |
| // We appear to have a valid level; break and continue. | |
| break; | |
| } | |
| else | |
| { | |
| // Invalid, show an error and then re-open the main | |
| // dialog. | |
| JOptionPane.showMessageDialog(null, "Couldn't find a valid level.dat file for the specified directory", "Minecraft XRAY error", JOptionPane.ERROR_MESSAGE); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // We chose one of the auto-detected worlds, continue. | |
| break; | |
| } | |
| } | |
| // Update our preferences | |
| this.xray_properties.setProperty("LAST_WORLD", availableWorlds.get(this.selectedWorld).getBasePath()); | |
| // set fullscreen from the dialog | |
| fullscreen = ResolutionDialog.selectedFullScreenValue; | |
| if (icons != null) | |
| Display.setIcon(icons); | |
| // Display.setIcon(); | |
| Display.setFullscreen(fullscreen); | |
| displayMode = ResolutionDialog.selectedDisplayMode; | |
| Display.setDisplayMode(displayMode); | |
| Display.setTitle(windowTitle); | |
| // TODO: actually do what the user requests here | |
| Display.setVSyncEnabled(true); | |
| Display.create(); | |
| screenWidth = displayMode.getWidth(); | |
| screenHeight = displayMode.getHeight(); | |
| } | |
| /*** | |
| * Checks for sanity of the minecraft environment | |
| */ | |
| private void checkMinecraftFiles() | |
| { | |
| if (MineCraftEnvironment.getMinecraftDirectory() == null) | |
| { | |
| JOptionPane.showMessageDialog(null, "OS not supported (" + System.getProperty("os.name") + "), please report.", "Minecraft XRAY error", JOptionPane.ERROR_MESSAGE); | |
| System.exit(0); | |
| } | |
| if (!MineCraftEnvironment.getMinecraftDirectory().exists()) | |
| { | |
| JOptionPane.showMessageDialog(null, "Minecraft directory not found: " + MineCraftEnvironment.getMinecraftDirectory().getAbsolutePath(), "Minecraft XRAY error", JOptionPane.ERROR_MESSAGE); | |
| System.exit(0); | |
| } | |
| if (!MineCraftEnvironment.getMinecraftDirectory().canRead()) | |
| { | |
| JOptionPane.showMessageDialog(null, "Minecraft directory not readable: " + MineCraftEnvironment.getMinecraftDirectory().getAbsolutePath(), "Minecraft XRAY error", | |
| JOptionPane.ERROR_MESSAGE); | |
| System.exit(0); | |
| } | |
| availableWorlds = MineCraftEnvironment.getAvailableWorlds(); | |
| // Add in a custom "Other..." world | |
| availableWorlds.add(new WorldInfo(true)); | |
| // Since we're adding our custom world, this'll actually never get hit. | |
| // Ah well. | |
| if (availableWorlds.size() == 0) | |
| { | |
| JOptionPane.showMessageDialog(null, "Minecraft directory found, but no minecraft levels available.", "Minecraft XRAY error", JOptionPane.ERROR_MESSAGE); | |
| System.exit(0); | |
| } | |
| } | |
| private void setChunkRange(int n) | |
| { | |
| if (n >= CHUNK_RANGES.length) | |
| n = CHUNK_RANGES.length - 1; | |
| if (n <= 0) | |
| n = 0; | |
| if (n == currentChunkRange) | |
| { | |
| return; | |
| } | |
| this.currentChunkRange = n; | |
| this.visible_chunk_range = CHUNK_RANGES[n]; | |
| this.needToReloadWorld = true; | |
| } | |
| private void setHighlightRange(int n) | |
| { | |
| if (n >= HIGHLIGHT_RANGES.length) | |
| n = HIGHLIGHT_RANGES.length - 1; | |
| if (n <= 0) | |
| n = 0; | |
| if (n == currentHighlightDistance) | |
| { | |
| return; | |
| } | |
| this.currentHighlightDistance = n; | |
| } | |
| /*** | |
| * Sets the world number we want to view | |
| * | |
| * @param world | |
| */ | |
| private void setMinecraftWorld(WorldInfo world) | |
| { | |
| this.world = world; | |
| this.level = new MinecraftLevel(world, minecraftTexture, paintingTexture, portalTexture, HIGHLIGHT_ORES); | |
| // determine which chunks are available in this world | |
| mapChunksToLoad = new LinkedList<Block>(); | |
| moveCameraToPlayerPos(); | |
| } | |
| /** | |
| * Sets the world number we want, and moves the camera to the specified | |
| * coordinates. There's a bit of code duplication going on here; should fix | |
| * that. | |
| * | |
| * @param world | |
| * @param camera_x | |
| * @param camera_z | |
| */ | |
| private void setMinecraftWorld(WorldInfo world, FirstPersonCameraController camera) | |
| { | |
| this.world = world; | |
| this.level = new MinecraftLevel(world, minecraftTexture, paintingTexture, portalTexture, HIGHLIGHT_ORES); | |
| // determine which chunks are available in this world | |
| mapChunksToLoad = new LinkedList<Block>(); | |
| this.camera = camera; | |
| initial_load_queued = false; | |
| initial_load_done = false; | |
| this.removeChunklistFromMap(level.removeAllChunksFromMinimap()); | |
| this.triggerChunkLoads(); | |
| } | |
| private void moveCameraToPosition(CameraPreset playerPos) | |
| { | |
| this.camera.getPosition().set(playerPos.block.x, playerPos.block.y, playerPos.block.z); | |
| this.camera.setYawAndPitch(180 + playerPos.yaw, playerPos.pitch); | |
| initial_load_queued = false; | |
| initial_load_done = false; | |
| this.removeChunklistFromMap(level.removeAllChunksFromMinimap()); | |
| this.triggerChunkLoads(); | |
| this.currentPosition = playerPos; | |
| } | |
| private void moveCameraToSpawnPoint() | |
| { | |
| this.moveCameraToPosition(level.getSpawnPoint()); | |
| } | |
| private void moveCameraToPlayerPos() | |
| { | |
| this.moveCameraToPosition(level.getPlayerPosition()); | |
| } | |
| private void moveCameraToNextPlayer() | |
| { | |
| this.moveCameraToPosition(level.getNextPlayerPosition(this.currentPosition)); | |
| } | |
| private void moveCameraToPreviousPlayer() | |
| { | |
| this.moveCameraToPosition(level.getPrevPlayerPosition(this.currentPosition)); | |
| } | |
| /** | |
| * Populates mapChunksToLoad with a list of chunks that need adding, based | |
| * on how far we've moved since our last known position. Realistically this | |
| * is never going to be more than one line at a time, though if someone's | |
| * getting hit with ridiculously low FPS or something, perhaps there could | |
| * end up being more. | |
| */ | |
| private void triggerChunkLoads() | |
| { | |
| int chunkX = level.getChunkX((int) -camera.getPosition().x); | |
| int chunkZ = level.getChunkZ((int) -camera.getPosition().z); | |
| if (initial_load_queued) | |
| { | |
| Chunk tempchunk; | |
| int dx = chunkX - cur_chunk_x; | |
| int dz = chunkZ - cur_chunk_z; | |
| int top_x = 0; | |
| int bot_x = 0; | |
| int top_z = 0; | |
| int bot_z = 0; | |
| // X | |
| if (dx < 0) | |
| { | |
| // System.out.println("Loading in chunks from the X range " + (cur_chunk_x-1-loadChunkRange) + " to " + (chunkX-loadChunkRange) + " (going down)"); | |
| top_x = cur_chunk_x - 1 - loadChunkRange; | |
| bot_x = chunkX - loadChunkRange; | |
| } | |
| else if (dx > 0) | |
| { | |
| // System.out.println("Loading in chunks from the X range " + (cur_chunk_x+1+loadChunkRange) + " to " + (chunkX+loadChunkRange) + " (going up)"); | |
| top_x = chunkX + loadChunkRange; | |
| bot_x = cur_chunk_x + 1 + loadChunkRange; | |
| } | |
| if (dx != 0) | |
| { | |
| for (int lx = bot_x; lx <= top_x; lx++) | |
| { | |
| for (int lz = chunkZ - loadChunkRange; lz <= chunkZ + loadChunkRange; lz++) | |
| { | |
| tempchunk = level.getChunk(lx, lz); | |
| if (tempchunk != null) | |
| { | |
| if (tempchunk.x == lx && tempchunk.z == lz) | |
| { | |
| if (!tempchunk.isOnMinimap) | |
| { | |
| drawChunkToMap(tempchunk.x, tempchunk.z); | |
| // minimap_changed = true; | |
| } | |
| continue; | |
| } | |
| level.clearChunk(lx, lz); | |
| } | |
| mapChunksToLoad.add(new Block(lx, 0, lz)); | |
| } | |
| } | |
| } | |
| // Z | |
| if (dz < 0) | |
| { | |
| // System.out.println("Loading in chunks from the Z range " + (cur_chunk_z-1-loadChunkRange) + " to " + (chunkZ-loadChunkRange) + " (going down)"); | |
| top_z = cur_chunk_z - 1 - loadChunkRange; | |
| bot_z = chunkZ - loadChunkRange; | |
| } | |
| else if (dz > 0) | |
| { | |
| // System.out.println("Loading in chunks from the Z range " + (cur_chunk_z+1+loadChunkRange) + " to " + (chunkZ+loadChunkRange) + " (going up)"); | |
| top_z = chunkZ + loadChunkRange; | |
| bot_z = cur_chunk_z + 1 + loadChunkRange; | |
| } | |
| if (dz != 0) | |
| { | |
| for (int lx = chunkX - loadChunkRange; lx <= chunkX + loadChunkRange; lx++) | |
| { | |
| for (int lz = bot_z; lz <= top_z; lz++) | |
| { | |
| tempchunk = level.getChunk(lx, lz); | |
| if (tempchunk != null) | |
| { | |
| if (tempchunk.x == lx && tempchunk.z == lz) | |
| { | |
| if (!tempchunk.isOnMinimap) | |
| { | |
| drawChunkToMap(tempchunk.x, tempchunk.z); | |
| // minimap_changed = true; | |
| } | |
| continue; | |
| } | |
| level.clearChunk(lx, lz); | |
| } | |
| mapChunksToLoad.add(new Block(lx, 0, lz)); | |
| } | |
| } | |
| } | |
| // Figure out if we need to trim our minimap (to prevent wrapping around) | |
| total_dX += dx; | |
| total_dZ += dz; | |
| ArrayList<Chunk> trimList = new ArrayList<Chunk>(); | |
| int i; | |
| if (Math.abs(total_dX) >= minimap_trim_chunks) | |
| { | |
| if (total_dX < 0) | |
| { | |
| // System.out.println("Clearing X from " + (chunkX-minimap_trim_chunk_distance+minimap_trim_chunks) + " to " + (chunkX-minimap_trim_chunk_distance)); | |
| for (i = chunkX - minimap_trim_chunk_distance + minimap_trim_chunks; i >= chunkX - minimap_trim_chunk_distance; i--) | |
| { | |
| trimList.addAll(level.removeChunkRowXFromMinimap(i)); | |
| } | |
| total_dX = -(Math.abs(total_dX) % minimap_trim_chunks); | |
| } | |
| else | |
| { | |
| // System.out.println("Clearing X from " + (chunkX+minimap_trim_chunk_distance-minimap_trim_chunks) + " to " + (chunkX+minimap_trim_chunk_distance)); | |
| for (i = chunkX + minimap_trim_chunk_distance - minimap_trim_chunks; i <= chunkX + minimap_trim_chunk_distance; i++) | |
| { | |
| trimList.addAll(level.removeChunkRowXFromMinimap(i)); | |
| } | |
| total_dX = total_dX % minimap_trim_chunks; | |
| } | |
| } | |
| if (Math.abs(total_dZ) >= minimap_trim_chunks) | |
| { | |
| if (total_dZ < 0) | |
| { | |
| // System.out.println("Clearing Z from " + (chunkZ-minimap_trim_chunk_distance+minimap_trim_chunks) + " to " + (chunkZ-minimap_trim_chunk_distance)); | |
| for (i = chunkZ - minimap_trim_chunk_distance + minimap_trim_chunks; i >= chunkZ - minimap_trim_chunk_distance; i--) | |
| { | |
| trimList.addAll(level.removeChunkRowZFromMinimap(i)); | |
| } | |
| total_dZ = -(Math.abs(total_dZ) % minimap_trim_chunks); | |
| } | |
| else | |
| { | |
| // System.out.println("Clearing Z from " + (chunkZ+minimap_trim_chunk_distance-minimap_trim_chunks) + " to " + (chunkZ+minimap_trim_chunk_distance)); | |
| for (i = chunkZ + minimap_trim_chunk_distance - minimap_trim_chunks; i <= chunkZ + minimap_trim_chunk_distance; i++) | |
| { | |
| trimList.addAll(level.removeChunkRowZFromMinimap(i)); | |
| } | |
| total_dZ = total_dZ % minimap_trim_chunks; | |
| } | |
| } | |
| removeChunklistFromMap(trimList); | |
| } | |
| else | |
| { | |
| // System.out.println("Loading world from X: " + (chunkX-loadChunkRange) + " - " + (chunkX+loadChunkRange) + ", Z: " + (chunkZ-loadChunkRange) + " - " + (chunkZ+loadChunkRange)); | |
| for (int lx = chunkX - loadChunkRange; lx <= chunkX + loadChunkRange; lx++) | |
| { | |
| for (int lz = chunkZ - loadChunkRange; lz <= chunkZ + loadChunkRange; lz++) | |
| { | |
| level.clearChunk(lx, lz); | |
| mapChunksToLoad.add(new Block(lx, 0, lz)); | |
| } | |
| } | |
| initial_load_queued = true; | |
| } | |
| cur_chunk_x = chunkX; | |
| cur_chunk_z = chunkZ; | |
| } | |
| /*** | |
| * handles all input on all screens | |
| * | |
| * @param timeDelta | |
| */ | |
| private void handleInput(float timeDelta) | |
| { | |
| int key; | |
| // distance in mouse movement from the last getDX() call. | |
| mouseX = Mouse.getDX(); | |
| // distance in mouse movement from the last getDY() call. | |
| mouseY = Mouse.getDY(); | |
| // we are on the main world screen or the level loading screen update the camera (but only if the mouse is grabbed) | |
| if (Mouse.isGrabbed()) | |
| { | |
| camera.incYaw(mouseX * MOUSE_SENSITIVITY); | |
| camera.incPitch(-mouseY * MOUSE_SENSITIVITY); | |
| } | |
| // | |
| // Keyboard commands (well, and mouse presses) | |
| // | |
| // Speed shifting | |
| if (Mouse.isButtonDown(0) || Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.SPEED_INCREASE))) | |
| { | |
| MOVEMENT_SPEED = 30.0f; | |
| } | |
| else if (Mouse.isButtonDown(1) || Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.SPEED_DECREASE))) | |
| { | |
| MOVEMENT_SPEED = 3.0f; | |
| } | |
| else | |
| { | |
| MOVEMENT_SPEED = 10.0f; | |
| } | |
| // Move forward | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.MOVE_FORWARD))) | |
| { | |
| camera.walkForward(MOVEMENT_SPEED * timeDelta, camera_lock); | |
| triggerChunkLoads(); | |
| } | |
| // Move backwards | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.MOVE_BACKWARD))) | |
| { | |
| camera.walkBackwards(MOVEMENT_SPEED * timeDelta, camera_lock); | |
| triggerChunkLoads(); | |
| } | |
| // Strafe Left | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.MOVE_LEFT))) | |
| { | |
| camera.strafeLeft(MOVEMENT_SPEED * timeDelta); | |
| triggerChunkLoads(); | |
| } | |
| // Strafe right | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.MOVE_RIGHT))) | |
| { | |
| camera.strafeRight(MOVEMENT_SPEED * timeDelta); | |
| triggerChunkLoads(); | |
| } | |
| // Fly Up | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.MOVE_UP))) | |
| { | |
| camera.moveUp(MOVEMENT_SPEED * timeDelta); | |
| triggerChunkLoads(); | |
| } | |
| // Fly Down | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.MOVE_DOWN))) | |
| { | |
| camera.moveUp(-MOVEMENT_SPEED * timeDelta); | |
| triggerChunkLoads(); | |
| } | |
| // Toggle minimap/largemap | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_MINIMAP); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| mapBig = !mapBig; | |
| keyPressed = key; | |
| } | |
| // Toggle highlightable ores | |
| needToReloadWorld = false; | |
| for (int i = 0; i < mineralToggle.length; i++) | |
| { | |
| if (Keyboard.isKeyDown(HIGHLIGHT_ORE_KEYS[i]) && keyPressed != HIGHLIGHT_ORE_KEYS[i]) | |
| { | |
| keyPressed = HIGHLIGHT_ORE_KEYS[i]; | |
| mineralToggle[i] = !mineralToggle[i]; | |
| needToReloadWorld = true; | |
| } | |
| } | |
| if (needToReloadWorld) | |
| { | |
| invalidateSelectedChunks(); | |
| } | |
| // Fullscreen | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_FULLSCREEN); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| switchFullScreenMode(); | |
| } | |
| // Toggle fullbright | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_FULLBRIGHT); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| setLightMode(!lightMode); | |
| updateRenderDetails(); | |
| } | |
| // Toggle ore highlighting | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_ORE_HIGHLIGHTING); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| highlightOres = !highlightOres; | |
| updateRenderDetails(); | |
| } | |
| // Move camera to spawn point | |
| key = key_mapping.get(KEY_ACTIONS.MOVE_TO_SPAWN); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| moveCameraToSpawnPoint(); | |
| } | |
| // Move camera to player position | |
| key = key_mapping.get(KEY_ACTIONS.MOVE_TO_PLAYER); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| moveCameraToPlayerPos(); | |
| } | |
| // Switch to the next available camera preset | |
| key = key_mapping.get(KEY_ACTIONS.MOVE_NEXT_CAMERAPOS); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| moveCameraToNextPlayer(); | |
| } | |
| // Switch to the previous camera preset | |
| key = key_mapping.get(KEY_ACTIONS.MOVE_PREV_CAMERAPOS); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| moveCameraToPreviousPlayer(); | |
| } | |
| // Increase light level | |
| key = key_mapping.get(KEY_ACTIONS.LIGHT_INCREASE); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| incLightLevel(); | |
| updateRenderDetails(); | |
| } | |
| // Decrease light level | |
| key = key_mapping.get(KEY_ACTIONS.LIGHT_DECREASE); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| decLightLevel(); | |
| updateRenderDetails(); | |
| } | |
| // Toggle position info popup | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_POSITION_INFO); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| levelInfoToggle = !levelInfoToggle; | |
| } | |
| // Toggle rendering info popup | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_RENDER_DETAILS); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| renderDetailsToggle = !renderDetailsToggle; | |
| } | |
| // Toggle bedrock rendering | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_BEDROCK); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| render_bedrock = !render_bedrock; | |
| invalidateSelectedChunks(true); | |
| updateRenderDetails(); | |
| } | |
| // Toggle explored-area highlighting | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_HIGHLIGHT_EXPLORED); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| highlight_explored = !highlight_explored; | |
| invalidateSelectedChunks(true); | |
| updateRenderDetails(); | |
| } | |
| // Toggle water rendering | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_WATER); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| render_water = !render_water; | |
| invalidateSelectedChunks(true); | |
| updateRenderDetails(); | |
| } | |
| // Toggle camera lock | |
| key = key_mapping.get(KEY_ACTIONS.TOGGLE_CAMERA_LOCK); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| camera_lock = !camera_lock; | |
| updateRenderDetails(); | |
| } | |
| // Toggle between Nether and Overworld | |
| key = key_mapping.get(KEY_ACTIONS.SWITCH_NETHER); | |
| if (Keyboard.isKeyDown(key) && keyPressed != key) | |
| { | |
| keyPressed = key; | |
| switchNether(); | |
| } | |
| // Temp routine to write the minimap out to a PNG (for debugging purposes) | |
| /* | |
| if (Keyboard.isKeyDown(Keyboard.KEY_P) && keyPressed != Keyboard.KEY_P) | |
| { | |
| keyPressed = Keyboard.KEY_P; | |
| BufferedImage bi = minimapTexture.getImage(); | |
| try { | |
| ImageIO.write(bi, "PNG", new File("/home/pez/xray.png")); | |
| System.out.println("Wrote minimap to disk."); | |
| } | |
| catch (Exception e) | |
| { | |
| // whatever | |
| } | |
| } | |
| */ | |
| // Handle changing chunk ranges (how far out we draw from the camera | |
| for (int i = 0; i < CHUNK_RANGES.length; i++) | |
| { | |
| if (Keyboard.isKeyDown(CHUNK_RANGES_KEYS[i]) && keyPressed != CHUNK_RANGES_KEYS[i]) | |
| { | |
| keyPressed = CHUNK_RANGES_KEYS[i]; | |
| setChunkRange(i); | |
| updateRenderDetails(); | |
| } | |
| } | |
| // Handle changing the ore highlight distances | |
| for (int i = 0; i < HIGHLIGHT_RANGES.length; i++) | |
| { | |
| if (Keyboard.isKeyDown(HIGHLIGHT_RANGES_KEYS[i]) && keyPressed != HIGHLIGHT_RANGES_KEYS[i]) | |
| { | |
| keyPressed = HIGHLIGHT_RANGES_KEYS[i]; | |
| setHighlightRange(i); | |
| updateRenderDetails(); | |
| } | |
| } | |
| // Release the mouse | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.RELEASE_MOUSE))) | |
| { | |
| Mouse.setGrabbed(false); | |
| } | |
| // Grab the mouse on a click | |
| if (Mouse.isButtonDown(0)) | |
| { | |
| Mouse.setGrabbed(true); | |
| } | |
| // Quit | |
| if (Keyboard.isKeyDown(key_mapping.get(KEY_ACTIONS.QUIT)) && Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) | |
| { | |
| done = true; | |
| } | |
| // Handle a requested window close | |
| if (Display.isCloseRequested()) | |
| { | |
| done = true; | |
| } | |
| // Clear out our keyPressed var if it's improperly-set | |
| if (keyPressed != -1) | |
| { | |
| if (!Keyboard.isKeyDown(keyPressed)) | |
| { | |
| keyPressed = -1; | |
| } | |
| } | |
| } | |
| /** | |
| * If we can, switches to/from nether. This will attempt to do an | |
| * approximate translation of your position, though that hasn't been tested | |
| * much, and won't totally line up with what Minecraft does. Note that | |
| * height is unaffected by this, so the adjacent portal might show up higher | |
| * or lower, depending on the local terrain. | |
| */ | |
| private void switchNether() | |
| { | |
| WorldInfo newworld = null; | |
| float camera_mult = 1.0f; | |
| if (world.isNether() && world.hasOverworld()) | |
| { | |
| this.cameraTextOverride = "equivalent Overworld location (approx.)"; | |
| newworld = world.getOverworldInfo(); | |
| camera_mult = 8.0f; | |
| } | |
| else if (!world.isNether() && world.hasNether()) | |
| { | |
| this.cameraTextOverride = "equivalent Nether location (approx.)"; | |
| newworld = world.getNetherInfo(); | |
| camera_mult = 1.0f / 8.0f; | |
| } | |
| if (newworld != null) | |
| { | |
| // A full reinitialization is kind of overkill, but whatever. | |
| FirstPersonCameraController cur_camera = this.camera; | |
| this.camera.processNetherWarp(camera_mult); | |
| initialize(); | |
| this.setMinecraftWorld(newworld, cur_camera); | |
| this.triggerChunkLoads(); | |
| } | |
| } | |
| private void invalidateSelectedChunks() | |
| { | |
| level.invalidateSelected(false); | |
| } | |
| private void invalidateSelectedChunks(boolean main_dirty) | |
| { | |
| level.invalidateSelected(main_dirty); | |
| } | |
| private void setLightMode(boolean lightMode) | |
| { | |
| this.lightMode = lightMode; | |
| if (lightMode) | |
| { | |
| GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Black Background | |
| GL11.glEnable(GL11.GL_FOG); | |
| } | |
| else | |
| { | |
| GL11.glClearColor(0.0f, 0.3f, 1.0f, 0.3f); // Blue Background | |
| GL11.glDisable(GL11.GL_FOG); | |
| } | |
| } | |
| /*** | |
| * Switches full screen mode | |
| */ | |
| private void switchFullScreenMode() | |
| { | |
| fullscreen = !fullscreen; | |
| try | |
| { | |
| Display.setFullscreen(fullscreen); | |
| } | |
| catch (Exception e) | |
| { | |
| e.printStackTrace(); | |
| } | |
| } | |
| /*** | |
| * Draw the spawn position to the minimap | |
| */ | |
| private void drawSpawnMarkerToMinimap() | |
| { | |
| Graphics2D g = minimapGraphics; | |
| CameraPreset spawn = level.getSpawnPoint(); | |
| int sy = getMinimapBaseY(spawn.block.cx) - (spawn.block.x % 16); | |
| int sx = (getMinimapBaseX(spawn.block.cz) + (spawn.block.z % 16)) % minimap_dim; | |
| g.setStroke(new BasicStroke(2)); | |
| g.setColor(Color.red.brighter()); | |
| g.drawOval(sx - 6, sy - 6, 11, 11); | |
| g.drawLine(sx - 8, sy, sx + 8, sy); | |
| g.drawLine(sx, sy - 8, sx, sy + 8); | |
| minimapTexture.update(); | |
| } | |
| /*** | |
| * Draw the current position to the minimap | |
| */ | |
| private void drawPlayerposMarkerToMinimap() | |
| { | |
| Graphics2D g = minimapGraphics; | |
| CameraPreset player = level.getPlayerPosition(); | |
| int py = getMinimapBaseY(player.block.cx) - (player.block.x % 16); | |
| int px = getMinimapBaseX(player.block.cz) + (player.block.z % 16); | |
| g.setStroke(new BasicStroke(2)); | |
| g.setColor(Color.yellow.brighter()); | |
| g.drawOval(px - 6, py - 6, 11, 11); | |
| g.drawLine(px - 8, py, px + 8, py); | |
| g.drawLine(px, py - 8, px, py + 8); | |
| minimapTexture.update(); | |
| } | |
| /*** | |
| * Main render loop | |
| * | |
| * @param timeDelta | |
| * @return | |
| */ | |
| private boolean render(float timeDelta) | |
| { | |
| // GL11.glLoadIdentity(); | |
| GL11.glLoadIdentity(); | |
| GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer | |
| // are we still loading the map? | |
| if (!map_load_started) | |
| { | |
| map_load_started = true; | |
| // drawMapMarkersToMinimap(); | |
| // minimapTexture.update(); | |
| setLightMode(true); // basically enable fog etc | |
| } | |
| // we are viewing a world | |
| GL11.glPushMatrix(); | |
| // change the camera to point a the right direction | |
| camera.applyCameraTransformation(); | |
| currentCameraPosX = (int) -camera.getPosition().x; | |
| currentCameraPosZ = (int) -camera.getPosition().z; | |
| // determine if we need to load new map chunks | |
| if (currentCameraPosX != levelBlockX || currentCameraPosZ != levelBlockZ || needToReloadWorld) | |
| { | |
| levelBlockX = currentCameraPosX; | |
| levelBlockZ = currentCameraPosZ; | |
| currentLevelX = level.getChunkX(levelBlockX); | |
| currentLevelZ = level.getChunkZ(levelBlockZ); | |
| } | |
| // draw the visible world | |
| int chunk_range = visible_chunk_range; | |
| if (HIGHLIGHT_RANGES[currentHighlightDistance] < chunk_range) | |
| { | |
| chunk_range = HIGHLIGHT_RANGES[currentHighlightDistance]; | |
| } | |
| GL11.glEnable(GL11.GL_TEXTURE_2D); | |
| GL11.glColor3f(1.0f, 1.0f, 1.0f); | |
| minecraftTexture.bind(); | |
| for (int lx = currentLevelX - visible_chunk_range; lx < currentLevelX + visible_chunk_range; lx++) | |
| { | |
| for (int lz = currentLevelZ - visible_chunk_range; lz < currentLevelZ + visible_chunk_range; lz++) | |
| { | |
| Chunk k = level.getChunk(lx, lz); | |
| if (k != null) | |
| { | |
| k.renderSolid(render_bedrock, render_water, highlight_explored); | |
| k.renderSelected(this.mineralToggle); | |
| paintingTexture.bind(); | |
| k.renderPaintings(); | |
| minecraftTexture.bind(); | |
| } | |
| } | |
| } | |
| for (int lx = currentLevelX - visible_chunk_range; lx < currentLevelX + visible_chunk_range; lx++) | |
| { | |
| for (int lz = currentLevelZ - visible_chunk_range; lz < currentLevelZ + visible_chunk_range; lz++) | |
| { | |
| Chunk k = level.getChunk(lx, lz); | |
| if (k != null) | |
| k.renderTransparency(); | |
| } | |
| } | |
| if (highlightOres) | |
| { | |
| GL11.glDisable(GL11.GL_DEPTH_TEST); | |
| long time = System.currentTimeMillis(); | |
| float alpha = (time % 1000) / 1000.0f; | |
| if (time % 2000 > 1000) | |
| alpha = 1.0f - alpha; | |
| alpha = 0.1f + (alpha * 0.8f); | |
| GL11.glColor4f(alpha, alpha, alpha, alpha); | |
| setLightLevel(20); | |
| GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE); | |
| for (int lx = currentLevelX - chunk_range; lx < currentLevelX + chunk_range; lx++) | |
| { | |
| for (int lz = currentLevelZ - chunk_range; lz < currentLevelZ + chunk_range; lz++) | |
| { | |
| Chunk k = level.getChunk(lx, lz); | |
| if (k != null) | |
| k.renderSelected(this.mineralToggle); | |
| } | |
| } | |
| GL11.glEnable(GL11.GL_DEPTH_TEST); | |
| GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); | |
| } | |
| setLightLevel(); | |
| GL11.glPopMatrix(); | |
| // draw the user interface (fps and map) | |
| drawUI(); | |
| return true; | |
| } | |
| /*** | |
| * Draw the ui | |
| */ | |
| private void drawUI() | |
| { | |
| framesSinceLastFps++; | |
| setOrthoOn(); // 2d mode | |
| drawMinimap(); | |
| drawFPSCounter(); | |
| drawMineralToggle(); | |
| if (levelInfoToggle) | |
| { | |
| drawLevelInfo(); | |
| } | |
| if (renderDetailsToggle) | |
| { | |
| drawRenderDetails(); | |
| } | |
| setOrthoOff(); // back to 3d mode | |
| } | |
| private void updateLevelInfo() | |
| { | |
| int labelX = 5; | |
| int valueX = 70; | |
| Graphics2D g = levelInfoTexture.getImage().createGraphics(); | |
| g.setBackground(Color.BLUE); | |
| g.clearRect(0, 0, 128, levelInfoTexture_h); | |
| g.setColor(Color.WHITE); | |
| g.fillRect(2, 2, 124, levelInfoTexture_h - 4); | |
| g.setFont(ARIALFONT); | |
| int chunkX = level.getChunkX(levelBlockX); | |
| int chunkZ = level.getChunkZ(levelBlockZ); | |
| g.setColor(Color.BLACK); | |
| g.drawString("Chunk X:", labelX, 22); | |
| g.setColor(Color.RED.darker()); | |
| g.drawString(Integer.toString(chunkX), valueX, 22); | |
| g.setColor(Color.BLACK); | |
| g.drawString("Chunk Z:", labelX, 22 + 16); | |
| g.setColor(Color.RED.darker()); | |
| g.drawString(Integer.toString(chunkZ), valueX, 22 + 16); | |
| g.setColor(Color.BLACK); | |
| g.drawString("World X:", labelX, 22 + 32); | |
| g.setColor(Color.RED.darker()); | |
| g.drawString(Integer.toString(levelBlockX), valueX, 22 + 32); | |
| g.setColor(Color.BLACK); | |
| g.drawString("World Z:", labelX, 22 + 16 + 32); | |
| g.setColor(Color.RED.darker()); | |
| g.drawString(Integer.toString(levelBlockZ), valueX, 22 + 16 + 32); | |
| g.setColor(Color.BLACK); | |
| g.drawString("World Y:", labelX, 22 + 16 + 32 + 16); | |
| g.setColor(Color.RED.darker()); | |
| g.drawString(Integer.toString((int) -camera.getPosition().y), valueX, 22 + 16 + 32 + 16); | |
| long heapSize = Runtime.getRuntime().totalMemory(); | |
| g.setColor(Color.BLACK); | |
| g.drawString("Memory Used", labelX, 22 + 16 + 32 + 16 + 25); | |
| g.setColor(Color.RED.darker()); | |
| g.drawString(Integer.toString((int) (heapSize / 1024 / 1024)) + " MB", 20, 22 + 16 + 32 + 16 + 25 + 20); | |
| levelInfoTexture.update(); | |
| } | |
| /** | |
| * Renders a text label in an info box, with differing fonts/colors for the | |
| * label and its value | |
| * | |
| * @param g | |
| * Graphics context to render to | |
| * @param x | |
| * Baseline x offset for the label | |
| * @param y | |
| * Baseline y offset for the label | |
| * @param label | |
| * The label to draw | |
| * @param labelColor | |
| * Label color | |
| * @param labelFont | |
| * Label font | |
| * @param value | |
| * The value | |
| * @param valueColor | |
| * Value color | |
| * @param valueFont | |
| * Value font | |
| */ | |
| private void infoboxTextLabel(Graphics2D g, int x, int y, String label, Color labelColor, Font labelFont, String value, Color valueColor, Font valueFont) | |
| { | |
| Rectangle2D bounds = labelFont.getStringBounds(label, g.getFontRenderContext()); | |
| g.setColor(labelColor); | |
| g.setFont(labelFont); | |
| g.drawString(label, x, y); | |
| g.setColor(valueColor); | |
| g.setFont(valueFont); | |
| g.drawString(value, (int) (x + bounds.getWidth()), y); | |
| } | |
| /** | |
| * Renders a slider-type graphic in an info box, including its label | |
| * | |
| * @param g | |
| * Graphics context to render to | |
| * @param x | |
| * Baseline X offset for the label | |
| * @param y | |
| * Baseline Y offset for the label | |
| * @param label | |
| * The label | |
| * @param labelColor | |
| * Label color | |
| * @param labelFont | |
| * Label font | |
| * @param line_h | |
| * How tall our individual lines are | |
| * @param slider_start_x | |
| * X offset to start the slider at | |
| * @param curval | |
| * Current value of slider | |
| * @param val_length | |
| * Length of slider data (array length, for us) | |
| */ | |
| private void infoboxSlider(Graphics2D g, int x, int y, String label, Color labelColor, Font labelFont, int line_h, int slider_start_x, int curval, int val_length) | |
| { | |
| int slider_top_y = y - line_h + 10; | |
| int slider_h = 8; | |
| int slider_end_x = renderDetails_w - 8; | |
| int marker_x = slider_start_x + (curval * ((slider_end_x - slider_start_x) / (val_length - 1))); | |
| // Label | |
| g.setColor(labelColor); | |
| g.setFont(labelFont); | |
| g.drawString(label, x, y); | |
| // Slider Base | |
| g.setColor(Color.BLACK); | |
| g.drawRect(slider_start_x, slider_top_y, slider_end_x - slider_start_x, slider_h); | |
| // Slider Location | |
| g.setColor(Color.RED); | |
| g.fillRect(marker_x, y - line_h + 8, 3, 13); | |
| } | |
| /** | |
| * Update our render-details infobox | |
| */ | |
| private void updateRenderDetails() | |
| { | |
| int line_h = 20; | |
| int x_off = 5; | |
| int line_count = 0; | |
| Graphics2D g = renderDetailsTexture.getImage().createGraphics(); | |
| g.setBackground(Color.WHITE); | |
| g.clearRect(0, 0, renderDetails_w, renderDetailsTexture.getTextureWidth()); | |
| g.setFont(DETAILFONT); | |
| g.setColor(Color.BLACK); | |
| if (!lightMode) | |
| { | |
| line_count++; | |
| infoboxTextLabel(g, x_off, line_count * line_h, "Fullbright: ", Color.BLACK, DETAILFONT, "On", Color.GREEN.darker(), DETAILVALUEFONT); | |
| } | |
| else | |
| { | |
| line_count++; | |
| infoboxSlider(g, x_off, line_count * line_h, "Light Level:", Color.BLACK, DETAILFONT, line_h, 90, currentLightLevel, lightLevelEnd.length); | |
| } | |
| line_count++; | |
| infoboxSlider(g, x_off, line_count * line_h, "Render Dist:", Color.BLACK, DETAILFONT, line_h, 90, currentChunkRange, CHUNK_RANGES.length); | |
| line_count++; | |
| infoboxSlider(g, x_off, line_count * line_h, "Highlight Dist:", Color.BLACK, DETAILFONT, line_h, 90, currentHighlightDistance, HIGHLIGHT_RANGES.length); | |
| if (!highlightOres) | |
| { | |
| line_count++; | |
| infoboxTextLabel(g, x_off, line_count * line_h, "Ore Highlight: ", Color.BLACK, DETAILFONT, "Off", Color.RED.darker(), DETAILVALUEFONT); | |
| } | |
| if (highlight_explored) | |
| { | |
| line_count++; | |
| infoboxTextLabel(g, x_off, line_count * line_h, "Explored Highlight: ", Color.BLACK, DETAILFONT, "On", Color.GREEN.darker(), DETAILVALUEFONT); | |
| } | |
| if (render_bedrock) | |
| { | |
| line_count++; | |
| infoboxTextLabel(g, x_off, line_count * line_h, "Bedrock: ", Color.BLACK, DETAILFONT, "On", Color.GREEN.darker(), DETAILVALUEFONT); | |
| } | |
| if (!render_water) | |
| { | |
| line_count++; | |
| infoboxTextLabel(g, x_off, line_count * line_h, "Water: ", Color.BLACK, DETAILFONT, "Off", Color.RED.darker(), DETAILVALUEFONT); | |
| } | |
| if (camera_lock) | |
| { | |
| line_count++; | |
| infoboxTextLabel(g, x_off, line_count * line_h, "Vertical Lock: ", Color.BLACK, DETAILFONT, "On", Color.green.darker(), DETAILVALUEFONT); | |
| } | |
| cur_renderDetails_h = (line_count + 1) * line_h - 8; | |
| g.setColor(Color.BLUE); | |
| g.setStroke(new BasicStroke(2)); | |
| g.drawRect(1, 1, renderDetails_w - 2, cur_renderDetails_h - 2); | |
| renderDetailsTexture.update(); | |
| } | |
| /*** | |
| * Draws our level info dialog to the screen | |
| */ | |
| private void drawLevelInfo() | |
| { | |
| int y = 48; | |
| if (renderDetailsToggle) | |
| { | |
| y += cur_renderDetails_h + 16; | |
| } | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); | |
| levelInfoTexture.bind(); | |
| SpriteTool.drawCurrentSprite(0, y, 128, levelInfoTexture_h, 0, 0, 1f, levelInfoTexture_h / 256f); | |
| } | |
| /*** | |
| * Draws our rendering details infobox to the screen | |
| */ | |
| private void drawRenderDetails() | |
| { | |
| renderDetailsTexture.bind(); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, .7f); | |
| SpriteTool.drawCurrentSprite(0, 48, renderDetails_w, cur_renderDetails_h, 0, 0, renderDetails_w / 256f, cur_renderDetails_h / 256f); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 1f); | |
| } | |
| /*** | |
| * Draw the mineral toggles | |
| */ | |
| private void drawMineralToggle() | |
| { | |
| int barWidth = 128 + 10 + 32; | |
| int barHeight = 42; | |
| int maxCols = 5; | |
| float mineralTogglebarLength; | |
| if ((mineralToggleTextures.length % maxCols) == 0) | |
| { | |
| mineralTogglebarLength = maxCols * barWidth; | |
| } | |
| else | |
| { | |
| mineralTogglebarLength = (mineralToggleTextures.length % maxCols) * barWidth; | |
| } | |
| float curX = (screenWidth / 2.0f) - (mineralTogglebarLength / 2.0f); | |
| float curY = screenHeight - barHeight; | |
| if (mineralToggleTextures.length > maxCols) | |
| { | |
| curY -= barHeight; | |
| } | |
| for (int i = 0; i < mineralToggleTextures.length; i++) | |
| { | |
| if (i == mineralToggleTextures.length - maxCols) | |
| { | |
| mineralTogglebarLength = maxCols * barWidth; | |
| curY += barHeight; | |
| curX = (screenWidth / 2.0f) - (mineralTogglebarLength / 2.0f); | |
| } | |
| if (mineralToggle[i]) | |
| { | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); | |
| GL11.glDisable(GL11.GL_TEXTURE_2D); | |
| SpriteTool.drawCurrentSprite(curX - 2, curY - 2, 36, 36, MineCraftConstants.precalcSpriteSheetToTextureX[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]], | |
| MineCraftConstants.precalcSpriteSheetToTextureY[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]], | |
| MineCraftConstants.precalcSpriteSheetToTextureX[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]] + TEX16, | |
| MineCraftConstants.precalcSpriteSheetToTextureY[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]] + TEX32); | |
| GL11.glEnable(GL11.GL_TEXTURE_2D); | |
| } | |
| else | |
| { | |
| GL11.glColor4f(0.5f, 0.5f, 0.5f, 1.0f); | |
| } | |
| minecraftTexture.bind(); | |
| SpriteTool.drawCurrentSprite(curX, curY, 32, 32, MineCraftConstants.precalcSpriteSheetToTextureX[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]], | |
| MineCraftConstants.precalcSpriteSheetToTextureY[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]], | |
| MineCraftConstants.precalcSpriteSheetToTextureX[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]] + TEX16, | |
| MineCraftConstants.precalcSpriteSheetToTextureY[blockDataToSpriteSheet[HIGHLIGHT_ORES[i].id]] + TEX32); | |
| SpriteTool.drawSpriteAbsoluteXY(mineralToggleTextures[i], curX + 32 + 10, curY + 7); | |
| curX += barWidth; | |
| } | |
| } | |
| /*** | |
| * Draws a simple fps counter on the top-left of the screen | |
| */ | |
| private void drawFPSCounter() | |
| { | |
| previousTime = time; | |
| time = System.nanoTime(); | |
| timeDelta = time - previousTime; | |
| if (time - lastFpsTime > NANOSPERSECOND) | |
| { | |
| fps = framesSinceLastFps; | |
| framesSinceLastFps = 0; | |
| lastFpsTime = time; | |
| updateFPSText = true; | |
| } | |
| if (updateFPSText) | |
| { | |
| if (levelInfoToggle) | |
| updateLevelInfo(); | |
| Graphics2D g = fpsTexture.getImage().createGraphics(); | |
| g.setBackground(Color.BLUE); | |
| g.clearRect(0, 0, 128, 32); | |
| g.setColor(Color.WHITE); | |
| g.fillRect(2, 2, 124, 28); | |
| g.setColor(Color.BLACK); | |
| g.setFont(ARIALFONT); | |
| g.drawString("FPS:", 10, 22); | |
| g.setColor(Color.RED.darker()); | |
| g.drawString(Integer.toString(fps), 60, 22); | |
| fpsTexture.update(); | |
| updateFPSText = false; | |
| } | |
| fpsTexture.bind(); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 0.7f); | |
| SpriteTool.drawSpriteAbsoluteXY(fpsTexture, 0, 0); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 1f); | |
| } | |
| /*** | |
| * Sets ortho (2d) mode | |
| */ | |
| public void setOrthoOn() | |
| { | |
| // prepare projection matrix to render in 2D | |
| GL11.glMatrixMode(GL11.GL_PROJECTION); | |
| GL11.glPushMatrix(); // preserve perspective view | |
| GL11.glLoadIdentity(); // clear the perspective matrix | |
| GL11.glOrtho( // turn on 2D mode | |
| // //viewportX,viewportX+viewportW, // left, right | |
| // //viewportY,viewportY+viewportH, // bottom, top !!! | |
| 0, screenWidth, // left, right | |
| screenHeight, 0, // bottom, top | |
| -500, 500); // Zfar, Znear | |
| // clear the modelview matrix | |
| GL11.glMatrixMode(GL11.GL_MODELVIEW); | |
| GL11.glPushMatrix(); // Preserve the Modelview Matrix | |
| GL11.glLoadIdentity(); // clear the Modelview Matrix | |
| // disable depth test so further drawing will go over the current scene | |
| GL11.glDisable(GL11.GL_DEPTH_TEST); | |
| } | |
| /** | |
| * Restore the previous mode | |
| */ | |
| public static void setOrthoOff() | |
| { | |
| // restore the original positions and views | |
| GL11.glMatrixMode(GL11.GL_PROJECTION); | |
| GL11.glPopMatrix(); | |
| GL11.glMatrixMode(GL11.GL_MODELVIEW); | |
| GL11.glPopMatrix(); | |
| // turn Depth Testing back on | |
| GL11.glEnable(GL11.GL_DEPTH_TEST); | |
| } | |
| /*** | |
| * draws the minimap or the big map | |
| */ | |
| private void drawMinimap() | |
| { | |
| if (mapBig) | |
| { | |
| // the big map | |
| // just draws the texture, but move the texture so the middle of the | |
| // screen is where we currently are | |
| minimapTexture.bind(); | |
| float vSizeFactor = .5f; | |
| float vTexX = -(1.0f / minimap_dim_f) * currentCameraPosZ; | |
| float vTexY = (1.0f / minimap_dim_f) * currentCameraPosX; | |
| float vTexZ = vSizeFactor; | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 0.7f); | |
| GL11.glPushMatrix(); | |
| GL11.glTranslatef((screenWidth / 2.0f), (screenHeight / 2.0f), 0.0f); | |
| GL11.glBegin(GL11.GL_TRIANGLE_STRIP); | |
| GL11.glTexCoord2f(vTexX - vTexZ, vTexY - vTexZ); | |
| GL11.glVertex2f(-minimap_dim_h_f, -minimap_dim_h_f); | |
| GL11.glTexCoord2f(vTexX + vTexZ, vTexY - vTexZ); | |
| GL11.glVertex2f(+minimap_dim_h_f, -minimap_dim_h_f); | |
| GL11.glTexCoord2f(vTexX - vTexZ, vTexY + vTexZ); | |
| GL11.glVertex2f(-minimap_dim_h_f, +minimap_dim_h_f); | |
| GL11.glTexCoord2f(vTexX + vTexZ, vTexY + vTexZ); | |
| GL11.glVertex2f(+minimap_dim_h_f, +minimap_dim_h_f); | |
| GL11.glEnd(); | |
| GL11.glPopMatrix(); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 1f); | |
| SpriteTool.drawSpriteAndRotateAndScale(minimapArrowTexture, screenWidth / 2.0f, screenHeight / 2.0f, camera.getYaw() + 90, 0.5f); | |
| } | |
| else | |
| { | |
| // the minimap | |
| // I set the minimap to 200 wide and tall | |
| // Interestingly, thanks to the fact that we're using GL11.GL_REPEAT on our | |
| // textures (via glTexParameter), we don't have to worry about checking | |
| // bounds here, etc. Or in other words, our map will automatically wrap for | |
| // us. Sweet! | |
| float vSizeFactor = 200.0f / minimap_dim_f; | |
| float vTexX = -(1.0f / minimap_dim_f) * currentCameraPosZ; | |
| float vTexY = (1.0f / minimap_dim_f) * currentCameraPosX; | |
| float vTexZ = vSizeFactor; | |
| minimapTexture.bind(); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 0.7f); | |
| GL11.glPushMatrix(); | |
| GL11.glTranslatef(screenWidth - 100, 100, 0.0f); | |
| GL11.glBegin(GL11.GL_TRIANGLE_STRIP); | |
| GL11.glTexCoord2f(vTexX - vTexZ, vTexY - vTexZ); | |
| GL11.glVertex2f(-100, -100); | |
| GL11.glTexCoord2f(vTexX + vTexZ, vTexY - vTexZ); | |
| GL11.glVertex2f(+100, -100); | |
| GL11.glTexCoord2f(vTexX - vTexZ, vTexY + vTexZ); | |
| GL11.glVertex2f(-100, +100); | |
| GL11.glTexCoord2f(vTexX + vTexZ, vTexY + vTexZ); | |
| GL11.glVertex2f(+100, +100); | |
| GL11.glEnd(); | |
| GL11.glPopMatrix(); | |
| GL11.glColor4f(1.0f, 1.0f, 1.0f, 1f); | |
| SpriteTool.drawSpriteAndRotateAndScale(minimapArrowTexture, screenWidth - 100, 100, camera.getYaw() + 90, 0.5f); | |
| } | |
| } | |
| /** | |
| * Returns the "base" minimap X coordinate, given chunk coordinate Z. The | |
| * "base" will be the upper right corner. | |
| * | |
| * In Minecraft, Z increases to the West, and decreases to the East, so our | |
| * minimap X coordinate will go up as chunkZ goes down. | |
| * | |
| * @param chunkZ | |
| * @return | |
| */ | |
| private int getMinimapBaseX(int chunkZ) | |
| { | |
| if (chunkZ < 0) | |
| { | |
| return ((Math.abs(chunkZ + 1) * 16) % minimap_dim) + 15; | |
| } | |
| else | |
| { | |
| return minimap_dim - (((chunkZ * 16) + 1) % minimap_dim); | |
| } | |
| } | |
| /** | |
| * Returns the "base" minimap Y coordinate, given chunk coordinate X. The | |
| * "base" will be the upper right corner. | |
| * | |
| * In Minecraft, X increases to the South, and decreases to the North, so | |
| * our minimap Y coordinate will go up as chunkX goes up (since the origin | |
| * of a texture is in the upper-left). | |
| * | |
| * @param chunkX | |
| * @return | |
| */ | |
| private int getMinimapBaseY(int chunkX) | |
| { | |
| if (chunkX < 0) | |
| { | |
| return (minimap_dim - ((Math.abs(chunkX) * 16) % minimap_dim)) % minimap_dim; | |
| } | |
| else | |
| { | |
| return (chunkX * 16) % minimap_dim; | |
| } | |
| } | |
| /** | |
| * Clears out the area on the minimap belonging to this chunk | |
| * | |
| * @param x | |
| * @param z | |
| */ | |
| public void removeMapChunkFromMap(int x, int z) | |
| { | |
| // minimapGraphics.setColor(new Color(0f, 0f, 0f, 1f)); | |
| // minimapGraphics.setComposite(AlphaComposite.Src); | |
| minimapGraphics.fillRect(getMinimapBaseX(z) - 15, getMinimapBaseY(x), 16, 16); | |
| level.getChunk(x, z).isOnMinimap = false; | |
| } | |
| /** | |
| * Loops through a list of chunks and removes them from the minimap | |
| * | |
| * @param trimList | |
| */ | |
| private void removeChunklistFromMap(ArrayList<Chunk> trimList) | |
| { | |
| minimapGraphics.setColor(new Color(0f, 0f, 0f, 0f)); | |
| minimapGraphics.setComposite(AlphaComposite.Src); | |
| boolean minimap_changed = false; | |
| for (Chunk tempchunk_trim : trimList) | |
| { | |
| removeMapChunkFromMap(tempchunk_trim.x, tempchunk_trim.z); | |
| minimap_changed = true; | |
| } | |
| if (minimap_changed) | |
| { | |
| minimapTexture.update(); | |
| } | |
| } | |
| /*** | |
| * draws a chunk to the (mini) map | |
| * | |
| * @param x | |
| * @param z | |
| */ | |
| public void drawChunkToMap(int x, int z) | |
| { | |
| Chunk c = level.getChunk(x, z); | |
| if (c != null) | |
| { | |
| c.isOnMinimap = true; | |
| } | |
| byte[] chunkData = level.getChunkData(x, z); | |
| int base_x = getMinimapBaseX(z); | |
| int base_y = getMinimapBaseY(x); | |
| Graphics2D g = minimapGraphics; | |
| for (int zz = 0; zz < 16; zz++) | |
| { | |
| for (int xx = 0; xx < 16; xx++) | |
| { | |
| // determine the top most visible block | |
| for (int yy = 127; yy >= 0; yy--) | |
| { | |
| int blockOffset = yy + (zz * 128) + (xx * 128 * 16); | |
| byte blockData = chunkData[blockOffset]; | |
| if (MineCraftConstants.blockDataToSpriteSheet[blockData] > -1) | |
| { | |
| if (blockData > -1) | |
| { | |
| Color blockColor = MineCraftConstants.blockColors[blockData]; | |
| if (blockColor != null) | |
| { | |
| // Previously we were using g.drawLine() here, but a minute-or-so's worth of investigating | |
| // didn't uncover a way to force that to be pixel-precise (the color would often bleed over | |
| // into adjoining pixels), so we're using g.fillRect() instead, which actually looks like it | |
| // is probably a faster operation anyway. I'm sure there'd have been a way to get drawLine | |
| // to behave, but c'est la vie! | |
| g.setColor(blockColor); | |
| g.fillRect(base_x - zz, base_y + xx, 1, 1); | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /*** | |
| * Draws the minimap sprites (currently just the arrow image) to their | |
| * textures | |
| */ | |
| private void createMinimapSprites() | |
| { | |
| // First the arrow | |
| Graphics2D g = minimapArrowTexture.getImage().createGraphics(); | |
| g.setColor(Color.red); | |
| g.setStroke(new BasicStroke(5)); | |
| g.drawLine(3, 16, 30, 24); | |
| g.drawLine(30, 24, 30, 8); | |
| g.drawLine(30, 8, 3, 16); | |
| minimapArrowTexture.update(); | |
| } | |
| /*** | |
| * cleanup | |
| */ | |
| private void cleanup() | |
| { | |
| Display.destroy(); | |
| } | |
| } |