com.telinc1.rpjg.map.Map.java Source code

Java tutorial

Introduction

Here is the source code for com.telinc1.rpjg.map.Map.java

Source

/*
 * 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;
    }
}