/*
* Copyright (c) 2003-onwards Shaven Puppy Ltd
* 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 'Shaven Puppy' 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 THE COPYRIGHT OWNER OR
* CONTRIBUTORS 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 worm;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.puppygames.applet.effects.EmitterFeature;
import org.lwjgl.util.Point;
import org.w3c.dom.Element;
import worm.features.DecalFeature;
import worm.features.LayersFeature;
import worm.features.TileSetFeature;
import com.shavenpuppy.jglib.Resources;
import com.shavenpuppy.jglib.resources.Feature;
import com.shavenpuppy.jglib.resources.MappedColor;
import com.shavenpuppy.jglib.resources.ResourceArray;
import com.shavenpuppy.jglib.sprites.Animation;
import com.shavenpuppy.jglib.sprites.Sprite;
import com.shavenpuppy.jglib.sprites.SpriteImage;
import com.shavenpuppy.jglib.util.Util;
import com.shavenpuppy.jglib.util.XMLUtil;
/**
* Describes an object that will be rendered on the map
* @author Cas
*/
public abstract class Tile extends Feature {
private static final long serialVersionUID = 1L;
private static final int MAX_TILES = Short.MAX_VALUE;
private static final String DEFAULT_FLOOR_COLOR = "floor";
private static final String DEFAULT_ITEM_COLOR = "item";
/** All tiles */
private static final Tile[] TILES = new Tile[MAX_TILES];
/** Maximum tile index */
private static int maxTileIndex = -1;
private static final Tile[] TEMP = new Tile[GameMap.LAYERS];
/*
* Feature data
*/
/** Layer */
private int layer;
/** Sprite layer, if different */
private int spriteLayer;
/** Index */
private short i;
/** Tile group name */
private String group;
/** An emitter */
private String emitter;
/** Emitter position */
private Point emitterPos;
/** Image */
private String image;
/** The tile's appearance, if animated */
private Animation animation;
/** Randomizer: automatically replace this tile with one from the following list when drawn */
private ResourceArray random;
/** Tile set */
private String set;
/** Next tile in sequence */
private String nextTile;
/** Tooltip */
private String tooltip;
/** @deprecated Decals */
@Deprecated
private List<DecalFeature> decals;
/** Layers - use instead of decals */
private String layers;
/** Colorize ? */
private MappedColor colored;
/** Spec: whether to register this tile or not. If spec==true, then this is a "specification" tile, just used as a template for other tiles */
private boolean spec;
/** Whether to apply attenuation */
private boolean attenuated;
/** Cost to traverse */
private float cost;
/*
* Transient data
*/
private transient SpriteImage imageResource;
private transient Tile nextTileResource;
private transient EmitterFeature emitterFeature;
private transient TileSetFeature tileSetFeature;
private transient LayersFeature layersFeature;
/**
* Get the tile of the specified index. Out-of-bounds tiles always return the EmptyTile
* @param idx The tile index
* @return a Tile, or the EmptyTile
*/
public static Tile getTile(int idx) {
if (idx < 0 || idx >= TILES.length) {
return Tile.getEmptyTile();
} else {
Tile ret = TILES[idx];
if (ret == null) {
return Tile.getEmptyTile();
} else {
return ret;
}
}
}
/**
* Get the largest tile index
* @return
*/
public static int getMaxTileIndex() {
return maxTileIndex;
}
/**
* @return Returns the tile array
*/
public static Tile[] getAllTiles() {
return TILES;
}
/**
* C'tor
*/
public Tile() {
setAutoCreated();
}
/**
* C'tor
*/
public Tile(String name) {
super(name);
setAutoCreated();
}
/* (non-Javadoc)
* @see com.shavenpuppy.jglib.resources.Feature#load(org.w3c.dom.Element, com.shavenpuppy.jglib.Resource.Loader)
*/
@Override
public void load(Element element, Loader loader) throws Exception {
super.load(element, loader);
// Load decals
List<Element> decalChildren = XMLUtil.getChildren(element, "decal");
if (decalChildren.size() > 0) {
decals = new ArrayList<DecalFeature>(decalChildren.size());
for (Element decalChild : decalChildren) {
DecalFeature decal = (DecalFeature) loader.load(decalChild);
decals.add(decal);
}
}
// Default value for "colored" depends on layer
if (colored == null) {
if (layer == 0) {
colored = new MappedColor();
colored.fromString(DEFAULT_FLOOR_COLOR);
} else if (layer == 1) {
colored = new MappedColor();
colored.fromString(DEFAULT_ITEM_COLOR);
}
}
}
/* (non-Javadoc)
* @see com.shavenpuppy.jglib.resources.Feature#doCreate()
*/
@Override
protected void doCreate() {
super.doCreate();
// Create decals
if (decals != null) {
for (Iterator<DecalFeature> it = decals.iterator(); it.hasNext(); ) {
DecalFeature decal = it.next();
decal.create();
}
}
}
/**
* @return true if the item is solid and can't be moved through or shot through
*/
public boolean isSolid() {
return false;
}
/**
* @return true if the item is impassable and can't be moved through but CAN be shot through
*/
public boolean isImpassable() {
return isSolid();
}
/**
* @return true if a bullet can pass through this tile
*/
public boolean isBulletThrough() {
return !isSolid();
}
/**
* @return Returns the animation for this tile, if it has no image
*/
public final Animation getAnimation() {
assert isCreated() : this+" is not created";
return animation;
}
/**
* @return Returns the image to use for this item, if it's not animated.
*/
public final SpriteImage getImage() {
assert isCreated() : this+" is not created";
return imageResource;
}
/**
* Stash in a sprite
* @param sprite
* @param toggled
*/
public void toSprite(Sprite sprite) {
assert isCreated() : this+" is not created";
if (sprite == null) {
return;
}
if (imageResource != null) {
sprite.setAnimation(null);
sprite.setImage(imageResource);
sprite.setVisible(true);
} else if (animation != null) {
if (sprite.getAnimation() != animation) {
sprite.setAnimation(animation);
}
sprite.setVisible(true);
} else {
sprite.setVisible(false);
}
if (spriteLayer != 0) {
sprite.setLayer(spriteLayer);
}
// // Ensure tiles on same layer as entities get drawn underneath when they're exactly aligned
// sprite.setYSortOffset(1);
}
/**
* @return Returns the index.
*/
public final short getIndex() {
return i;
}
/**
* @return Returns the layer.
*/
public final int getLayer() {
return layer;
}
/* (non-Javadoc)
* @see com.shavenpuppy.jglib.resources.Feature#doRegister()
*/
@Override
protected void doRegister() {
if (spec) {
return;
}
if (TILES[i] != null) {
throw new RuntimeException("Tile "+i+" is already defined as "+TILES[i]);
}
TILES[i] = this;
maxTileIndex = Math.max(i, maxTileIndex);
}
/* (non-Javadoc)
* @see com.shavenpuppy.jglib.resources.Feature#doDeregister()
*/
@Override
protected void doDeregister() {
if (spec) {
return;
}
TILES[i] = null;
}
/**
* Called when the tile is drawn on the map
* @param map
* @param x
* @param y
*/
public void onDrawn(GameMap map, int x, int y) {
}
/**
* Called when the tile is removed from the map
* @param map
* @param x
* @param y
*/
public void onCleared(GameMap map, int x, int y) {
}
/**
* Draw to a map
* @param map
* @param x
* @param y
* @return the tile just drawn if using a sequence
*/
public Tile toMap(GameMap map, int x, int y, boolean useRandom) {
assert isCreated() : this+" is not created";
// // Maybe clear item. Do this first so undo works
// if (getLayer() == 0 && isSolid()) {
// map.clearItem(x, y);
// }
// If we've got a random set, pick one
Tile next = getNextTile();
if (random != null && useRandom) {
Tile t = (Tile) random.getResource(Util.random(0, random.getNumResources() - 1));
map.setTile(x, y, t.getLayer(), t);
} else if (next != null && useRandom) {
// Check existing tile. If it's the same as this tile, use the next tile.
Tile old = map.getTile(x, y, getLayer());
if (old == this) {
next.toMap(map, x, y, false);
return next;
} else {
map.setTile(x, y, getLayer(), this);
}
} else {
// Otherwise, it's just a straightforward draw...
map.setTile(x, y, getLayer(), this);
}
return null;
}
/**
* @return Returns the emitter, if any
*/
public final EmitterFeature getEmitter() {
return emitterFeature;
}
/**
* @return Returns the emitter position; may be null (only valid if there's an emitter feature)
*/
public final Point getEmitterPos() {
return emitterPos;
}
/**
* @return Returns the group.
*/
public final String getGroup() {
return group;
}
/**
* @return the Empty Tile which is always tile 0
*/
public static Tile getEmptyTile() {
return getTile(0);
}
/**
* @return Returns the nextTileResource.
*/
public Tile getNextTile() {
if (nextTile != null && nextTileResource == null) {
// Parse nextTile as a number and use that index instead
return getTile(Integer.parseInt(nextTile));
}
return nextTileResource;
}
/**
* @return Returns the tooltip.
*/
public String getTooltip() {
if (tooltip == null) {
return Resources.getTag(getClass());
} else {
return tooltip;
}
}
/**
* @param tileSet
*/
public void setTileSet(TileSetFeature tileSet) {
this.tileSetFeature = tileSet;
}
/**
* @return Returns the tileSetFeature.
*/
public TileSetFeature getTileSet() {
return tileSetFeature;
}
/**
* @return Returns the spriteLayer.
*/
public int getSpriteLayer() {
return spriteLayer;
}
/**
* @return a List of DecalFeatures, or null, if there are no decals
*/
public List<DecalFeature> getDecals() {
return decals;
}
/**
* @return Returns the colored.
*/
public MappedColor getColor() {
return colored;
}
/**
* @return layers
*/
public LayersFeature getLayers() {
return layersFeature;
}
/**
* Processes this tile with the incoming {@link MapProcessor}, when the map is loaded
* @param processor
* @param x
* @param y
*/
public void process(MapProcessor processor, int x, int y) {
}
/**
* @return true if we're to attenuate this tile's main sprite image
*/
public boolean isAttenuated() {
return attenuated;
}
/**
* @return the cost to traverse the tile
*/
public float getCost() {
return cost;
}
}