de.ailis.wlandsuite.game.parts.TileMap.java Source code

Java tutorial

Introduction

Here is the source code for de.ailis.wlandsuite.game.parts.TileMap.java

Source

/*
 * $Id$
 * Copyright (C) 2006 Klaus Reimer <k@ailis.de>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

package de.ailis.wlandsuite.game.parts;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;

import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import de.ailis.wlandsuite.common.exceptions.GameException;
import de.ailis.wlandsuite.huffman.HuffmanInputStream;
import de.ailis.wlandsuite.huffman.HuffmanOutputStream;
import de.ailis.wlandsuite.huffman.HuffmanTree;
import de.ailis.wlandsuite.io.SeekableInputStream;
import de.ailis.wlandsuite.io.SeekableOutputStream;
import de.ailis.wlandsuite.utils.StringUtils;
import de.ailis.wlandsuite.utils.XmlUtils;

/**
 * The Tile Map maps eachs square to a specific tile of the selected tileset.
 *
 * @author Klaus Reimer (k@ailis.de)
 * @version $Revision$
 */

public class TileMap {
    /** The tile map. First index is y, second is x */
    private final int[][] map;

    /** A unknown 32 bit address */
    private int unknown;

    /**
     * Constructs a new Tile Map with the specified map size. Map size must be
     * 32 or 64. If an other size is specified then an IllegalArgumentException
     * is thrown
     *
     * @param mapSize
     *            The map size (32 or 64)
     * @throws IllegalArgumentException
     *             If illegal map size was specified
     */

    public TileMap(final int mapSize) {
        if (mapSize != 32 && mapSize != 64) {
            throw new IllegalArgumentException("Illegal map size specified: " + mapSize);
        }
        this.map = new int[mapSize][mapSize];
    }

    /**
     * Creates and returns a new Tile Map from XML.
     *
     * @param element
     *            The XML element
     * @param mapSize
     *            The map size
     * @param backgroundTile
     *            The background tile which is used for ".." bytes
     * @return The new Tile Map
     */

    public static TileMap read(final Element element, final int mapSize, final int backgroundTile) {
        TileMap tileMap;
        String data;
        String c;
        int i;
        int b;

        // Create the new Tile Map
        tileMap = new TileMap(mapSize);

        tileMap.unknown = StringUtils.toInt(element.attributeValue("unknown", "0"));
        data = element.getTextTrim();
        i = 0;
        for (int y = 0; y < mapSize; y++) {
            for (int x = 0; x < mapSize; x++) {
                try {
                    c = data.substring(i * 3, i * 3 + 2);
                } catch (final StringIndexOutOfBoundsException e) {
                    throw new GameException(
                            "Tile map is corrupt: " + (mapSize * mapSize - (y * mapSize + x)) + " bytes missing");
                }
                if (c.equals("..")) {
                    b = backgroundTile;
                } else {
                    try {
                        b = Integer.valueOf(c, 16);
                    } catch (final NumberFormatException e) {
                        throw new GameException("Illegal data in tile map at y=" + y + " x=" + x);
                    }
                }
                tileMap.map[y][x] = b;
                i++;
            }
        }

        // Returns the newly created Tile Map
        return tileMap;
    }

    /**
     * Creates and returns a new Tile Map read from the specified input stream.
     * The stream must be positioned on the beginning of the tile map header
     * (Which is the 32 bit map size).
     *
     * @param stream
     *            The stream to read the tile map from
     * @param mapSize
     *            Defines the tilemap size. If this is 0 then the size is read
     *            from the tile map itself. This only works for the original
     *            game files. So if a map size != 0 is specified then it is
     *            assumed that we have to read Displacer's hacked data files.
     * @return The new Tile Map
     * @throws IOException
     *             When file operation fails.
     */

    public static TileMap read(final SeekableInputStream stream, final int mapSize) throws IOException {
        InputStream huffmanStream;
        int realMapSize;

        final boolean compressed = mapSize == 0;

        // Read map size from stream
        if (compressed) {
            realMapSize = stream.readSignedInt();
            if (realMapSize == 32 * 32) {
                realMapSize = 32;
            } else if (realMapSize == 64 * 64) {
                realMapSize = 64;
            } else {
                throw new IOException("Invalid Tile Map header");
            }
        } else
            realMapSize = mapSize;

        // Create the new TileMap
        final TileMap tileMap = new TileMap(realMapSize);

        // Read the unknown 32 bit value
        if (compressed) {
            tileMap.unknown = stream.readSignedInt();
            huffmanStream = new HuffmanInputStream(stream);
        } else {
            huffmanStream = stream;
        }

        // Read the tile map data
        for (int y = 0; y < realMapSize; y++) {
            for (int x = 0; x < realMapSize; x++) {
                tileMap.map[y][x] = huffmanStream.read();
            }
        }

        // Return the tile map
        return tileMap;
    }

    /**
     * Returns the tile map as XML.
     *
     * @param backgroundTile
     *            The background tile for which ".." is printed
     * @return The tile map as XML
     */

    public Element toXml(final int backgroundTile) {
        Element element;
        StringWriter text;
        PrintWriter writer;
        int mapSize;

        // Create the root element
        element = XmlUtils.createElement("tileMap");
        if (this.unknown != 0) {
            element.addAttribute("unknown", StringUtils.toHex(this.unknown));
        }

        // Determine the map size
        mapSize = this.map.length;

        // Write the tile map content
        text = new StringWriter();
        writer = new PrintWriter(text);
        writer.println();
        for (int y = 0; y < mapSize; y++) {
            writer.print("    ");
            for (int x = 0; x < mapSize; x++) {
                int b;

                if (x > 0) {
                    writer.print(" ");
                }

                b = this.map[y][x];
                if (b == backgroundTile) {
                    writer.append("..");
                } else {
                    writer.format("%02x", new Object[] { b });
                }
            }
            writer.println();
        }
        writer.print("  ");
        element.add(DocumentHelper.createText(text.toString()));

        // Return the XML code
        return element;
    }

    /**
     * Writes the Tile Map to the specified stream
     *
     * @param stream
     *            The stream to write the Tile Map to
     * @param compress
     *            If the tile map should be compressed
     * @throws IOException
     *             When file operation fails.
     */

    public void write(final SeekableOutputStream stream, final boolean compress) throws IOException {
        int mapSize;
        ByteArrayOutputStream byteStream;
        byte[] bytes;
        HuffmanTree tree;
        HuffmanOutputStream huffmanStream;

        // Determine the map size
        mapSize = this.map.length;

        // Write the Tile Map header
        if (compress) {
            stream.writeInt(mapSize * mapSize);
            stream.writeInt(this.unknown);
        }

        // Write the Tile Map data
        byteStream = new ByteArrayOutputStream();
        for (int y = 0; y < mapSize; y++) {
            for (int x = 0; x < mapSize; x++) {
                byteStream.write(this.map[y][x]);
            }
        }
        bytes = byteStream.toByteArray();
        if (compress) {
            tree = HuffmanTree.create(bytes);
            huffmanStream = new HuffmanOutputStream(stream, tree);
            huffmanStream.write(bytes);
            huffmanStream.flush();
        } else {
            stream.write(bytes);
        }
    }

    /**
     * Returns the tile for the specified position
     *
     * @param x
     *            The X position
     * @param y
     *            The Y position
     * @return The action selector
     */

    public int getTile(final int x, final int y) {
        return this.map[y][x];
    }

    /**
     * Sets the tile at the specified position.
     *
     * @param x
     *            The X coordinate
     * @param y
     *            The Y coordinate
     * @param tile
     *            The tile to set
     */

    public void setTile(final int x, final int y, final int tile) {
        this.map[y][x] = tile;
    }

    /**
     * Returns the unknown 32 bit address.
     *
     * @return The unknown 32 bit address
     */

    public int getUnknown() {
        return this.unknown;
    }
}