package it.marteEngine; import org.newdawn.slick.AngelCodeFont; import org.newdawn.slick.Animation; import org.newdawn.slick.BigImage; import org.newdawn.slick.Color; import org.newdawn.slick.Image; import org.newdawn.slick.Music; import org.newdawn.slick.SlickException; import org.newdawn.slick.Sound; import org.newdawn.slick.SpriteSheet; import org.newdawn.slick.SpriteSheetFont; import org.newdawn.slick.UnicodeFont; import org.newdawn.slick.font.effects.ColorEffect; import org.newdawn.slick.tiled.TiledMap; import org.newdawn.slick.util.Log; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.IOException; import java.io.InputStream; import java.util.StringTokenizer; /** * The XMLResourceLoader parses an xml document and reads the following known * elements: * basedir path(req) * sound key file * music key file * image key file transparentColor(opt) * sheet key file width height transparentColor(opt) * anim key imgName frameDuration frames(opt) flipVertical(opt) flipHorizontal(opt) loop(default=true) * angelcodefont key fontFile imageFile * unicodefont key file fontSize(default=12) * spritesheetfont key file firstchar * map key file * param key value * <p/> * A parameter followed by (req) means Required and by (opt) means Optional. * Some parameters are optional because they have a default value. The file * parameter is relative to the basedir. * <p/> * Examples: * <sound key="jump" file="jump.wav" /> * <music key="sleepy" file="sleep.wav" /> * <image key="ship" file="ship.png" /> * <sheet key="homer" file="homer.png" width="25" height="25" transparentColor="000000" /> * <anim key="homer" imgName="homer" frameDuration="250" row="0" frames="1,2,3,4,5" * flipVertical="true" flipHorizontal="true" loop="false"/> * <unicodefont key="default" file="gbb.ttf" fontSize="20" /> * <angelcodefont key="font" fontFile="font.fnt" imageFile="font.png" /> * <map key="level1" file="level1.tmx" /> * <param key="bossLevel" value="11" /> */ public class XMLResourceLoader { private Element[] NO_ELEMENTS = new Element[0]; private String baseDir; private Element rootElement; public void load(InputStream in) throws IOException { try { DocumentBuilder builder = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); Document document = builder.parse(in); rootElement = document.getDocumentElement(); validateResourceFile(); Element[] elements = getElementsByTagName("basedir"); setBaseDirectory(elements[0].getAttribute("path")); for (Element element : getElementsByTagName("sound")) { loadSound(element); } for (Element element : getElementsByTagName("music")) { loadMusic(element); } for (Element element : getElementsByTagName("image")) { loadImage(element); } for (Element element : getElementsByTagName("sheet")) { loadSpriteSheet(element); } for (Element element : getElementsByTagName("anim")) { loadAnimation(element); } for (Element element : getElementsByTagName("angelcodefont")) { loadAngelCodeFont(element); } for (Element element : getElementsByTagName("unicodefont")) { loadUnicodeFont(element); } for (Element element : getElementsByTagName("spritesheetfont")) { loadSpriteSheetFont(element); } for (Element element : getElementsByTagName("map")) { loadTiledMap(element); } for (Element element : getElementsByTagName("param")) { loadParameter(element); } } catch (Exception e) { Log.error(e); throw new IOException( "Unable to load resource configuration file", e); } } private void validateResourceFile() throws IOException { if (!rootElement.getNodeName().equals("resources")) { throw new IOException("Not a resource configuration file"); } } private Element[] getElementsByTagName(String elementName) { NodeList nodes = rootElement.getElementsByTagName(elementName); int nodeCount = nodes.getLength(); if (nodeCount != 0) { Element[] elements = new Element[nodeCount]; for (int i = 0; i < nodeCount; i++) { Element element = (Element) nodes.item(i); elements[i] = element; } return elements; } else { return NO_ELEMENTS; } } private void setBaseDirectory(String baseDirectory) throws SlickException { Log.debug("Setting base directory to " + baseDirectory); if (baseDirectory == null || baseDirectory.isEmpty()) { throw new SlickException("Could not find required BaseDir element."); } baseDir = baseDirectory; if (!baseDir.endsWith("/")) { baseDir += "/"; } } private void loadSound(Element element) throws SlickException { checkRequiredAttributes(element, "key", "file"); String key = element.getAttribute("key"); String file = element.getAttribute("file"); Log.debug(formatLoadMsg("sound", key, file)); ResourceManager.addSound(key, new Sound(baseDir + file)); } private void loadMusic(Element element) throws SlickException { checkRequiredAttributes(element, "key", "file"); String key = element.getAttribute("key"); String file = element.getAttribute("file"); Log.debug(formatLoadMsg("music", key, file)); ResourceManager.addMusic(key, new Music(baseDir + file)); } private void loadImage(Element element) throws SlickException { checkRequiredAttributes(element, "key", "file"); String key = element.getAttribute("key"); String file = element.getAttribute("file"); Color transparentColor = null; if (element.hasAttribute("transparentColor")) { transparentColor = decodeColor(element); } Image image; try { image = new Image(baseDir + file, transparentColor); } catch (SlickException ex) { // To support large textures on older graphic cards Log.info("Using BigImage."); image = new BigImage(baseDir + file); } Log.debug(formatLoadMsg("image", key, file)); ResourceManager.addImage(key, image); } private void loadSpriteSheet(Element element) throws SlickException { checkRequiredAttributes(element, "key", "file", "width", "height"); String key = element.getAttribute("key"); String file = element.getAttribute("file"); int width = parseIntAttribute(element, "width"); int height = parseIntAttribute(element, "height"); Color transparentColor = null; if (element.hasAttribute("transparentColor")) { transparentColor = decodeColor(element); } Log.debug(String.format( "Loading spritesheet key=%s file=%s width=%s height=%s", key, file, width, height)); ResourceManager.addSpriteSheet(key, new SpriteSheet(baseDir + file, width, height, transparentColor)); } private Color decodeColor(Element element) { String transparentColorAsText = element .getAttribute("transparentColor"); if (transparentColorAsText != null && !transparentColorAsText.isEmpty()) { return Color.decode(transparentColorAsText); } else { return null; } } private void loadAnimation(Element element) { checkRequiredAttributes(element, "key", "imgName", "frameDuration"); String key = element.getAttribute("key"); String imgName = element.getAttribute("imgName"); int frameDuration = parseIntAttribute(element, "frameDuration"); boolean flipHorizontal = false, flipVertical = false; int[] frames; int row = 0; boolean loop = true; if (!ResourceManager.hasSpriteSheet(imgName)) { throw new IllegalArgumentException("Animation " + key + " needs spritesheet " + imgName + " but it has not been loaded."); } SpriteSheet sheet = ResourceManager.getSpriteSheet(imgName); if (element.hasAttribute("frames")) { String framesAsText = element.getAttribute("frames"); frames = readFrameIndexes(framesAsText); } else { frames = new int[sheet.getHorizontalCount()]; for (int i = 0; i < sheet.getHorizontalCount(); i++) { frames[i] = i; } } if (element.hasAttribute("flipHorizontal")) { flipHorizontal = parseBooleanAttribute(element, "flipHorizontal"); } if (element.hasAttribute("flipVertical")) { flipVertical = parseBooleanAttribute(element, "flipVertical"); } if (element.hasAttribute("row")) { row = parseIntAttribute(element, "row"); } if (element.hasAttribute("loop")) { loop = parseBooleanAttribute(element, "loop"); } Animation anim = buildAnimationFromFrames(sheet, row, frames, frameDuration, flipHorizontal, flipVertical); anim.setLooping(loop); Log.debug(formatLoadMsg("animation", key, "spritesheet", imgName)); ResourceManager.addAnimation(key, anim); } private int[] readFrameIndexes(String framesAsText) { StringTokenizer tokenizer = new StringTokenizer(framesAsText, ","); int frames[] = new int[tokenizer.countTokens()]; for (int i = 0; tokenizer.hasMoreTokens(); i++) { frames[i] = Integer.parseInt(tokenizer.nextToken().trim()); } return frames; } /** * Create an animation from a spritesheet row. * * @param sheet * The spritesheet that contains the individual images. * @param row * The vertical index in the spritesheet to read the animation * from, zero based. * @param frames * The horizontal indexes of the frames that need to be included * in the animation, zero based. * @param frameDuration * The duration of 1 frame in the resulting animation. * @param flipHorizontal * If true flips each frame in the animation on the x axis * @param flipVertical * If true flips each frame in the animation on the y axis * @return An animation parsed from the spritesheet */ private Animation buildAnimationFromFrames(SpriteSheet sheet, int row, int[] frames, int frameDuration, boolean flipHorizontal, boolean flipVertical) { Animation animation = new Animation(false); for (int frameIndex : frames) { Image img = sheet.getSubImage(frameIndex, row); if (flipHorizontal || flipVertical) { img = img.getFlippedCopy(flipHorizontal, flipVertical); } animation.addFrame(img, frameDuration); } return animation; } private void loadAngelCodeFont(Element element) throws SlickException { checkRequiredAttributes(element, "key", "fontFile", "imageFile"); String key = element.getAttribute("key"); String fontFile = element.getAttribute("fontFile"); String imageFile = element.getAttribute("imageFile"); Log.debug(String.format( "Loading Angelcode font key=%s imagefile=%s fontfile=%s", key, imageFile, fontFile)); AngelCodeFont font = new AngelCodeFont(baseDir + fontFile, baseDir + imageFile, true); ResourceManager.addFont(key, font); } @SuppressWarnings("unchecked") private void loadUnicodeFont(Element element) throws SlickException { checkRequiredAttributes(element, "key", "file"); String key = element.getAttribute("key"); String ttfFile = element.getAttribute("file"); int fontSize = 12; if (element.hasAttribute("fontSize")) { fontSize = parseIntAttribute(element, "fontSize"); } Log.debug(formatLoadMsg("Unicode font", key, ttfFile)); UnicodeFont unicodeFont = new UnicodeFont(baseDir + ttfFile, fontSize, false, false); unicodeFont.getEffects().add(new ColorEffect()); unicodeFont.addAsciiGlyphs(); unicodeFont.loadGlyphs(); unicodeFont.setDisplayListCaching(true); ResourceManager.addFont(key, unicodeFont); } private void loadSpriteSheetFont(Element element) { checkRequiredAttributes(element, "key", "file", "firstchar"); String key = element.getAttribute("key"); String imgName = element.getAttribute("file"); char startingChar = element.getAttribute("firstchar").charAt(0); if (!ResourceManager.hasSpriteSheet(imgName)) { throw new IllegalArgumentException("Spritesheetfont " + key + " needs spritesheet " + imgName + " but it has not been loaded."); } SpriteSheet sheet = ResourceManager.getSpriteSheet(imgName); Log.debug(formatLoadMsg("SpriteSheet font", key, imgName)); SpriteSheetFont font = new SpriteSheetFont(sheet, startingChar); ResourceManager.addFont(key, font); } private void loadTiledMap(Element element) throws SlickException { checkRequiredAttributes(element, "key", "file"); String key = element.getAttribute("key"); String file = element.getAttribute("file"); Log.debug(formatLoadMsg("Tiled map", key, file)); TiledMap tiledMap = new TiledMap(baseDir + file); ResourceManager.addTiledMap(key, tiledMap); } private void loadParameter(Element element) throws SlickException { checkRequiredAttributes(element, "key", "value"); String key = element.getAttribute("key"); String value = element.getAttribute("value"); Log.debug(formatLoadMsg("Parameter", key, "value", value)); ResourceManager.setParameter(key, value); } private void checkRequiredAttributes(Element element, String... attributes) { boolean requiredAttributeMissing = false; for (String attribute : attributes) { if (!element.hasAttribute(attribute)) { requiredAttributeMissing = true; } } if (requiredAttributeMissing) { StringBuffer missingAttributesMessage = new StringBuffer( "Required attribute(s) missing: "); for (String attribute : attributes) { if (!element.hasAttribute(attribute)) { missingAttributesMessage.append(attribute); missingAttributesMessage.append(", "); } } // Remove trailing ", " int length = missingAttributesMessage.length(); missingAttributesMessage.delete(length - 2, length); missingAttributesMessage.append(" from element ").append( element.getNodeName()); throw new IllegalArgumentException( missingAttributesMessage.toString()); } } private int parseIntAttribute(Element element, String attributeName) { return Integer.parseInt(element.getAttribute(attributeName)); } private boolean parseBooleanAttribute(Element element, String attributeName) { return Boolean.parseBoolean(element.getAttribute(attributeName)); } private String formatLoadMsg(String type, String key, String value) { return formatLoadMsg(type, key, "file", value); } private String formatLoadMsg(String type, String key, String valueName, String value) { StringBuffer buf = new StringBuffer("Loading "); buf.append(type).append(" key=").append(key).append(" ") .append(valueName).append("=").append(value); return buf.toString(); } }