Java tutorial
/* * Copyright 2015-2016 Telinc1 * * 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.telinc1.rpjg.map; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.lwjgl.opengl.GL11; import com.telinc1.nfls.field.FieldBatch; import com.telinc1.nfls.field.list.FieldBatchList; import com.telinc1.rpjg.map.event.Event; import com.telinc1.rpjg.reference.GameOptions; import com.telinc1.rpjg.texture.Color; import com.telinc1.rpjg.tileset.Tileset; import com.telinc1.rpjg.tileset.tile.Tile; import com.telinc1.rpjg.util.LoadingException; public class Map { /** * The width of the map. */ private int width; /** * The height of the map. */ private int height; /** * The in-game name of the map. */ private String name; /** * The map's floor.<br /> * Positive values (e.g. 1) display as <code>Map's Name 1F</code>.<br /> * Negative values (e.g. -1) display as <code>Map's Name B1</code>. */ private byte floor; /** * The index of the border tile, which will fill nonexistent tiles on the map. */ private int border; /** * The clear color of the map. * @see org.lwjgl.opengl.GL11#glClearColor(float, float, float, float) */ private Color clearColor; /** * Contains all of the tiles on the map. */ private int[] tiles; /** * Contains the collision data for all of the tiles on the map. */ private int[] collision; /** * Contains all of the events on the map. */ private List<Event> events; /** * Loads in and returns the given map. * * @param name - The name of the directory which contains the map. * @return The loaded map, or null if not found or invalid. */ public static Map loadMap(String name) { String path = GameOptions.ASSETS_LOCATION + "maps/" + name + "/"; Map map = new Map(); try { // Load map header. DataInputStream header = new DataInputStream(new FileInputStream(new File(path + "header.dat"))); map.width = header.readShort(); map.height = header.readShort(); Tileset.loadTileset(header.readShort()); map.border = header.readShort(); map.clearColor = new Color(header.readInt()); GL11.glClearColor(map.clearColor.r, map.clearColor.g, map.clearColor.b, map.clearColor.a); map.name = header.readUTF(); map.floor = header.readByte(); header.close(); // Load tiles. map.tiles = new int[map.width * map.height]; DataInputStream tiles = new DataInputStream(new FileInputStream(new File(path + "tiles.dat"))); for (int i = 0; i < map.tiles.length; i++) { map.tiles[i] = tiles.readShort(); } tiles.close(); // Load collision data. map.collision = new int[map.width * map.height]; DataInputStream collision = new DataInputStream(new FileInputStream(new File(path + "collision.dat"))); for (int j = 0; j < map.collision.length; j++) { map.collision[j] = collision.readByte(); } collision.close(); // Load event data. map.events = new ArrayList<>(); DataInputStream events = new DataInputStream(new FileInputStream(new File(path + "events.dat"))); short amount = events.readShort(); for (int k = 0; k < amount; k++) { map.addEvent(events); } events.close(); } catch (IOException e) { e.printStackTrace(); } return map; } /** * @return The map's width. */ public int getWidth() { return this.width; } /** * @return The map's height. */ public int getHeight() { return this.height; } /** * @returns The tile indexes for all tiles on the map. */ public int[] getMapTiles() { return this.tiles; } /** * @return The collision data for all tiles on the map. */ public int[] getMapCollision() { return this.collision; } /** * Gets the tileset index of the tile at the given coordinates. * * @param x - The X coordinate of the requested tile. * @param y - The Y coordinate of the requested tile. * @return The index of the tile, from the tileset. */ public int getTileIndexAt(int x, int y) { if (x >= this.getWidth() || x < 0 || y >= this.getHeight() || y < 0) { return this.border; } return this.getMapTiles()[this.resolveCoordinates(x, y)]; } /** * Gets the tile at the specified coordinates. * * @param x - The X coordinate of the requested tile. * @param y - The Y coordinate of the requested tile. * @return The tile at the given coordinates. */ public Tile getTileAt(int x, int y) { return Tileset.loadedTileset.getTile(this.getTileIndexAt(x, y)); } /** * Gets the collision data for the given tile.<br /> * It's a bitfield in the format <code>0bSSShhhhs</code>, where: * <ul> * <li><code>s</code> determines if the tile is solid;</li> * <li><code>h</code> defines the tile's elevation. 0 is a special elevation which refers to all elevations.</li> * <li><code>S</code> is a special override, used for bridges, water, etc.</li> * </ul> * * @param x - The X coordinate of the tile. * @param y - The Y coordinate of the tile. * @return The collision data for the given tile. */ public int getCollisionAt(int x, int y) { if (x >= this.getWidth() || x < 0 || y >= this.getHeight() || y < 0) { return 1; } return this.getMapCollision()[this.resolveCoordinates(x, y)]; } /** * Checks if a tile on the map is solid.<br /> * Doesn't have to be a solid tile - a solid event will return true as well. * * @param x - The X coordinate to check. * @param y - The Y coordinate to check. * @return Whether or not the given tile is solid. */ public boolean isTileSolid(int x, int y) { Event event = this.getEventAt(x, y); if (event != null && event.isSolid()) { return true; } return (this.getCollisionAt(x, y) & 1) != 0; } /** * Gets the elevation of the tile at the given coordinates.<br /> * It should be noted that <code>0</code> is a special value, * which refers to all elevation levels. * * @param x - The X coordinate to check. * @param y - The Y coordinate to check. * @return The elevation of the given tile. */ public int getTileHeight(int x, int y) { return (this.getCollisionAt(x, y) >> 1) & 0b1111; } /** * Turns a pair of x,y coordinates to their index in the tiles array. * * @param x - The X coordinate. * @param y - The y coordinate * @return The index which corresponds to those coordinates. */ public int resolveCoordinates(int x, int y) { return (this.getWidth() * y) + x; } /** * Sets the tile at the given positions.<br /> * Doesn't do anything if the given tile is invalid. * * @param x - The X coordinate of the tile. * @param y - The Y coordinate of the tile. * @param tileIndex - The tile to set. * @param collision - The collision of the new tile. * @return - True if the tile exists. */ public boolean setTileAt(int x, int y, int tileIndex, int collision) { // Check if the desired tile is out of the map. if (x >= this.getWidth() || x < 0 || y >= this.getHeight() || y < 0) { return false; } if (Tileset.loadedTileset.getSize() > tileIndex) { return false; } this.tiles[this.resolveCoordinates(x, y)] = tileIndex; this.collision[this.resolveCoordinates(x, y)] = collision; return true; } /** * Tries to load and add an event to the map. * * @param data - The stream to pull the event's data out of. * @return The index the event was added under, or -1 if an error occurred. */ public int addEvent(DataInputStream data) { try { byte type = data.readByte(); // Don't instantiate nonexistent events. // Also, let's not add multiple players. if (type == 0 || !Event.exists(type)) { return -1; } // Create a new instance of the specified type. Event event = Event.getEvent(type); event.loadEvent(data); // Add the event to the map. this.events.add(event); int index = this.events.indexOf(event); event.setIndex(index); return index; } catch (IOException e) { e.printStackTrace(); } return -1; } /** * Loads the stored data (if any) for all events on the map.<br /> * * @param batch - The batch to read from. */ public void loadEvents(FieldBatchList data) { for (FieldBatch batch : data.getData()) { try { int index = batch.getInt("Index").getData(); Event event = this.getEvents().get(index); if (event != null) { event.loadEvent(batch); } } catch (NullPointerException e) { e.printStackTrace(); continue; } catch (IndexOutOfBoundsException e) { e.printStackTrace(); continue; } catch (LoadingException e) { e.printStackTrace(); continue; } } } /** * @return A list of all loaded events on the map. */ public List<Event> getEvents() { return this.events; } /** * Retrieves an event, located on the given coordinates. * * @param x - The X coordinate of the requested event. * @param y - The Y coordinate of the requested event. * @return The event at the coordinate pair, or null if there's no event there. */ public Event getEventAt(int x, int y) { for (Event event : this.getEvents()) { if (event.getX() == x && event.getY() == y) { return event; } } return null; } /** * Checks if there's an interactable object at the given coordinates * and returns it if it exists. * * @param x - The X coordinate of the object. * @param y - The Y coordinate of the object. * @return The interactable object, or <code>null</code> if it doesn't exist. */ public IInteractable getInteractableObject(int x, int y) { Event event = this.getEventAt(x, y); if (event != null && event instanceof IInteractable) { return (IInteractable) event; } Tile tile = this.getTileAt(x, y); if (tile != null && tile instanceof IInteractable) { return (IInteractable) tile; } return null; } }