/*
* 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 com.shavenpuppy.jglib.sprites;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.shavenpuppy.jglib.IResource;
import com.shavenpuppy.jglib.Resource;
import com.shavenpuppy.jglib.XMLResourceWriter;
/**
* An Animation specifies a sequence of Commands which control the image displayed
* by an Animated thing.
*/
public class Animation extends Resource implements Appearance {
private static final long serialVersionUID = 1L;
/** Known commands */
private static final Map<String, Class<? extends IResource>> COMMAND_TAG_MAP = new HashMap<String, Class<? extends IResource>>();
static {
COMMAND_TAG_MAP.put("angle", AngleCommand.class);
COMMAND_TAG_MAP.put("offset", OffsetCommand.class);
COMMAND_TAG_MAP.put("move", MoveCommand.class);
COMMAND_TAG_MAP.put("scale", ScaleCommand.class);
COMMAND_TAG_MAP.put("frame", FrameCommand.class);
COMMAND_TAG_MAP.put("flag", FlagCommand.class);
COMMAND_TAG_MAP.put("alpha", AlphaCommand.class);
COMMAND_TAG_MAP.put("color", ColorCommand.class);
COMMAND_TAG_MAP.put("animcolor", AnimColorCommand.class);
COMMAND_TAG_MAP.put("goto", GotoCommand.class);
COMMAND_TAG_MAP.put("next", NextCommand.class);
COMMAND_TAG_MAP.put("delay", RandomDelayCommand.class);
COMMAND_TAG_MAP.put("random", RandomGotoCommand.class);
COMMAND_TAG_MAP.put("event", EventCommand.class);
COMMAND_TAG_MAP.put("loop", LoopCommand.class);
COMMAND_TAG_MAP.put("repeat", RepeatCommand.class);
COMMAND_TAG_MAP.put("sound", SoundCommand.class);
COMMAND_TAG_MAP.put("label", LabelCommand.class);
COMMAND_TAG_MAP.put("sub", SubCommand.class);
COMMAND_TAG_MAP.put("return", ReturnCommand.class);
COMMAND_TAG_MAP.put("frameset", FrameListCommand.class);
}
/** Commands */
private Command[] command;
/** Labels */
private Map<String, Integer> labels;
/**
* Constructor for Animation.
*/
public Animation() {
super();
}
/**
* Constructor for Animation.
*/
public Animation(String name) {
super(name);
}
@Override
public void load(Element element, Resource.Loader loader) throws Exception {
labels = null; // for reload...
try {
// Add the known commands - you have to specify other commands yourself
// in the XML
loader.pushMap(COMMAND_TAG_MAP);
// The child tags of the element should all be descended from Commands.
NodeList childTagList = element.getChildNodes();
ArrayList<IResource> commandList = new ArrayList<IResource>(childTagList.getLength());
for (int i = 0; i < childTagList.getLength(); i ++) {
if (childTagList.item(i) instanceof Element) {
Element childElement = (Element) childTagList.item(i);
IResource childResource = loader.load(childElement);
if (!(childResource instanceof Command)) {
throw new Exception("Only Command resources are allowed inside an Animation; got a "+childResource);
}
if (childResource instanceof LabelCommand) {
String id = ((LabelCommand) childResource).getID();
if (labels == null) {
labels = new HashMap<String, Integer>();
} else if (labels.containsKey(id)) {
throw new Exception("Animation "+this+" already contains label "+id);
}
labels.put(id, new Integer(commandList.size()));
} else {
commandList.add(childResource);
}
}
}
command = new Command[commandList.size()];
commandList.toArray(command);
} finally {
// Make sure we remove those commandTags
loader.popMap();
}
}
@Override
protected void doToXML(XMLResourceWriter writer) throws IOException {
boolean wasCompact = writer.isCompact();
writer.setCompact(true);
for (int i = 0; i < command.length; i ++) {
command[i].toXML(writer);
}
writer.setCompact(wasCompact);
}
@Override
protected void doCreate() {
// Create all the commands
for (int i = 0; i < command.length; i ++) {
command[i].create();
}
}
@Override
protected void doDestroy() {
// Destroy all the commands
for (int i = 0; i < command.length; i ++) {
command[i].destroy();
}
}
/**
* Animate an animated thing. The animated thing should pass in its current
* sequence and current tick.
*
* The tick is increased by one, and if this is greater than or equal to
* the duration specified for the current sequence, the sequence number is
* increased by one and the new sequence's command is executed. If the
* duration of the command is 0 then the next command is executed, and so on,
* until a duration > 0 is encountered.
*
* If there are no more sequences then nothing happens.
*
* When a command is executed, it is applied to the Animated thing.
*
* @param animated The thing that wants to be animated
*/
public void animate(Sprite animated) {
int currentSequence;
do {
currentSequence = animated.getSequence();
// Do nothing if the current sequence is not valid
if (currentSequence < 0 || currentSequence >= command.length) {
return;
}
} while (command[currentSequence].execute(animated));
}
/**
* Get the sequence number of the label.
* @param id
* @return -1 if the label isn't found
*/
public int getLabel(String id) {
if (labels == null) {
return -1;
}
Integer ret = labels.get(id);
if (ret == null) {
return -1;
} else {
return ret.intValue();
}
}
@Override
public boolean toSprite(Sprite target) {
target.setAnimation(this);
return true;
}
}