/* * 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.features; import java.util.List; import net.puppygames.applet.Screen; import net.puppygames.applet.effects.Emitter; import net.puppygames.applet.effects.EmitterFeature; import org.lwjgl.util.Color; import org.lwjgl.util.Point; import org.lwjgl.util.ReadablePoint; import org.w3c.dom.Element; import worm.AttenuatedColor; import worm.ColorAttenuationConstants; import worm.MapRenderer; import worm.Worm; import worm.animation.ThingWithLayers; import com.shavenpuppy.jglib.Point2f; import com.shavenpuppy.jglib.resources.Feature; import com.shavenpuppy.jglib.resources.MappedColor; import com.shavenpuppy.jglib.sprites.Animation; import com.shavenpuppy.jglib.sprites.Sprite; import com.shavenpuppy.jglib.sprites.SpriteAllocator; import com.shavenpuppy.jglib.sprites.SpriteImage; import com.shavenpuppy.jglib.util.FPMath; import com.shavenpuppy.jglib.util.XMLUtil; /** * Describes layers of animation */ public class LayersFeature extends Feature { /** * The name of shadows colours */ public static final String SHADOW_COLOR_NAME = "shadow".intern(); private static final long serialVersionUID = 1L; private static final Color TEMP = new Color(); /* * Resource data */ /** The various layers */ private Layer[] sprite; private EmitterLayer[] emitter; private float scale = 1.0f; private Point offset; private float ySortOffset; /* * Transient data */ /** Whether we have any "colored" sprites */ private transient boolean hasColored; /** Whether any of the colored sprites are attenuated */ private transient boolean hasAttenuated; /** Layers */ private static class Layer extends Feature { private String id; private int layer; private int subLayer; private Point2f offset; private float ySortOffset; private String animation; private String image; private boolean doChildOffset; // Either we use colored, to color the whole sprite; or we use topColored and bottomColored, to color the top and bottom of the sprite private MappedColor colored; private MappedColor topColored, bottomColored; /** If true, then we'll attenuate the colors with the level colors attenuation colour according to approx. distance from map centre */ private boolean attenuated; private transient Animation animationResource; private transient SpriteImage imageResource; public Layer() { setAutoCreated(); setSubResource(true); } } /** Emitters */ private static class EmitterLayer extends Feature { private String emitter; private Point2f offset; private float ySortOffset; private boolean doChildOffset; private transient EmitterFeature emitterFeature; public EmitterLayer() { setAutoCreated(); setSubResource(true); } } /** * C'tor */ public LayersFeature() { setAutoCreated(); } /** * C'tor * @param name */ public LayersFeature(String name) { super(name); setAutoCreated(); } @Override public void load(Element element, Loader loader) throws Exception { super.load(element, loader); List<Element> layerElements = XMLUtil.getChildren(element, "sprite"); if (layerElements.size() > 0) { sprite = new Layer[layerElements.size()]; int count = 0; for (Element child : layerElements) { sprite[count] = new Layer(); sprite[count].load(child, loader); count ++; } } List<Element> emitterLayerElements = XMLUtil.getChildren(element, "emitter"); if (emitterLayerElements.size() > 0) { emitter = new EmitterLayer[emitterLayerElements.size()]; int emitterCount = 0; for (Element child : emitterLayerElements) { emitter[emitterCount] = new EmitterLayer(); emitter[emitterCount].load(child, loader); emitterCount ++; } } } @Override protected void doCreate() { super.doCreate(); if (sprite != null) { for (Layer element : sprite) { element.create(); if (element.colored != null || element.topColored != null && element.bottomColored != null) { hasColored = true; if (element.attenuated) { hasAttenuated = true; } } } } if (emitter != null) { for (EmitterLayer element : emitter) { element.create(); } } } @Override protected void doDestroy() { super.doDestroy(); if (sprite != null) { for (Layer element : sprite) { element.destroy(); } } if (emitter != null) { for (EmitterLayer element : emitter) { element.destroy(); } } } /** * Find a layer by name * @param id Name; cannot be null * @return an index, or -1 */ public int getLayer(String id) { if (sprite == null) { return -1; } for (int i = 0; i < sprite.length; i ++) { if (id.equals(sprite[i].id)) { return i; } } return -1; } /** * Create the sprites for an entity * @param allocator The screen to create the sprites on * @param owner Owner of the sprites */ public void createSprites(SpriteAllocator allocator, ThingWithLayers owner) { createSprites(allocator, 0.0f, 0.0f, owner); } /** * Create the sprites for an entity * @param allocator The screen to create the sprites on * @param mapX location of the entity's centre * @param mapY location of the entity's centre * @param owner Owner of the sprites */ public void createSprites(SpriteAllocator allocator, float mapX, float mapY, ThingWithLayers owner) { if (sprite == null) { owner.setSprites(new Sprite[0]); return; } Sprite[] s = new Sprite[sprite.length]; float ox, oy; if (offset != null) { ox = offset.getX(); oy = offset.getY(); } else { ox = 0.0f; oy = 0.0f; } for (int i = 0; i < s.length; i ++) { s[i] = allocator.allocateSprite(owner); } owner.setSprites(s); for (int i = 0; i < s.length; i ++) { if (scale != 1.0f) { s[i].setScale(FPMath.fpValue(scale)); } s[i].setLayer(sprite[i].layer); s[i].setSubLayer(sprite[i].subLayer); if (sprite[i].offset != null) { s[i].setOffset((sprite[i].offset.getX() + ox) * scale, (sprite[i].offset.getY() + oy) * scale); } else { s[i].setOffset(ox * scale, oy * scale); } s[i].setYSortOffset((ySortOffset + sprite[i].ySortOffset) * scale); if (sprite[i].doChildOffset) { s[i].setDoChildOffset(true); } if (sprite[i].animationResource != null) { s[i].setAnimation(sprite[i].animationResource); } else { s[i].setImage(sprite[i].imageResource); } } createColors(s); updateColors(s, mapX, mapY); } public void updateLocation(Sprite[] existingSprite, float x, float y) { for (Sprite element : existingSprite) { element.setLocation(x, y); } } /** * Create the emitters at a specific location * @param screen TODO * @param x * @param y * @return an array of emitters (may be empty but won't be null) */ public Emitter[] createEmitters(Screen screen, float x, float y) { if (emitter == null) { return new Emitter[0]; } Emitter[] e = new Emitter[emitter.length]; for (int i = 0; i < e.length; i ++) { if (emitter[i].emitterFeature != null) { e[i] = emitter[i].emitterFeature.spawn(screen); float ox, oy; if (emitter[i].offset != null) { ox = emitter[i].offset.getX() * scale; oy = emitter[i].offset.getY() * scale; } else { ox = 0; oy = 0; } e[i].setLocation(x + ox, y + oy); e[i].setYOffset(emitter[i].ySortOffset * scale); } } return e; } /** * Update an existing array of emitters * @param existingEmitters * @param x TODO * @param y TODO */ public void updateEmitters(Emitter[] existingEmitters, float x, float y) { for (int i = 0; i < existingEmitters.length; i ++) { if (existingEmitters[i] != null) { float ox, oy; if (emitter[i].offset != null) { ox = emitter[i].offset.getX() * scale; oy = emitter[i].offset.getY() * scale; } else { ox = 0; oy = 0; } existingEmitters[i].setLocation(x + ox, y + oy); } } } public void updateScale(Sprite[] existingSprites, float newScale) { for (int i = 0; i < existingSprites.length; i ++) { existingSprites[i].setScale(FPMath.fpValue(newScale * scale)); if (sprite[i].offset != null) { existingSprites[i].setOffset(sprite[i].offset.getX() * newScale * scale, sprite[i].offset.getY() * newScale * scale); } existingSprites[i].setYSortOffset(sprite[i].ySortOffset * newScale * scale); } } public void updateColors(Sprite[] existingSprites, float mapX, float mapY) { if (!hasColored) { return; } float mapWidth, mapHeight, ratio = 0.0f; if (hasAttenuated) { mapWidth = Worm.getGameState().getMap().getWidth(); mapHeight = Worm.getGameState().getMap().getHeight(); ratio = ColorAttenuationConstants.dist(mapX / MapRenderer.TILE_SIZE, mapY / MapRenderer.TILE_SIZE, mapWidth, mapHeight) / ColorAttenuationConstants.getMaxDist(); } updateColors(existingSprites, ratio); } public void updateColors(Sprite[] existingSprites, float ratio) { if (!hasColored) { return; } updateColors(existingSprites, ratio, ratio, ratio, ratio, 0, 0, 0, 0); } public void updateColors(Sprite[] existingSprites, float ratio00, float ratio10, float ratio11, float ratio01, int fade00, int fade10, int fade11, int fade01) { if (!hasColored) { return; } for (int i = 0; i < existingSprites.length; i ++) { if ((sprite[i].colored != null || sprite[i].topColored != null && sprite[i].bottomColored != null) && sprite[i].attenuated) { if (existingSprites[i].getColor(0) instanceof AttenuatedColor) { // animcolor command puts ordinary Colors back into sprites. Bah ((AttenuatedColor) existingSprites[i].getColor(0)).setRatio(ratio00); ((AttenuatedColor) existingSprites[i].getColor(1)).setRatio(ratio10); ((AttenuatedColor) existingSprites[i].getColor(2)).setRatio(ratio11); ((AttenuatedColor) existingSprites[i].getColor(3)).setRatio(ratio01); ((AttenuatedColor) existingSprites[i].getColor(0)).setFade(fade00); ((AttenuatedColor) existingSprites[i].getColor(1)).setFade(fade10); ((AttenuatedColor) existingSprites[i].getColor(2)).setFade(fade11); ((AttenuatedColor) existingSprites[i].getColor(3)).setFade(fade01); } } } } private void createColors(Sprite[] existingSprites) { if (!hasColored) { return; } for (int i = 0; i < existingSprites.length; i ++) { if (sprite[i].colored != null) { MappedColor c = sprite[i].colored; if (c != null) { if (sprite[i].attenuated) { if (c.getColorName() != null && c.getColorName().intern() == SHADOW_COLOR_NAME) { existingSprites[i].setColor(0, new AttenuatedColor(c, true)); existingSprites[i].setColor(1, new AttenuatedColor(c, true)); existingSprites[i].setColor(2, new AttenuatedColor(c, true)); existingSprites[i].setColor(3, new AttenuatedColor(c, true)); } else { existingSprites[i].setColor(0, new AttenuatedColor(c)); existingSprites[i].setColor(1, new AttenuatedColor(c)); existingSprites[i].setColor(2, new AttenuatedColor(c)); existingSprites[i].setColor(3, new AttenuatedColor(c)); } } else { existingSprites[i].setColors(c); } } } else if (sprite[i].topColored != null && sprite[i].bottomColored != null) { MappedColor topColor = sprite[i].topColored; MappedColor bottomColor = sprite[i].bottomColored; if (topColor != null && bottomColor != null) { if (sprite[i].attenuated) { existingSprites[i].setColor(0, new AttenuatedColor(bottomColor)); existingSprites[i].setColor(1, new AttenuatedColor(bottomColor)); existingSprites[i].setColor(2, new AttenuatedColor(topColor)); existingSprites[i].setColor(3, new AttenuatedColor(topColor)); } else { existingSprites[i].setColor(0, bottomColor); existingSprites[i].setColor(1, bottomColor); existingSprites[i].setColor(2, topColor); existingSprites[i].setColor(3, topColor); } } } } } /** * @return the scale */ public float getScale() { return scale; } /** * @return the offset */ public ReadablePoint getOffset() { return offset; } }