/******************************************************************************* * Copyright (c) 2015 * * 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 jsettlers.graphics.map.draw.settlerimages; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; import jsettlers.common.material.EMaterialType; import jsettlers.common.movable.EDirection; import jsettlers.common.movable.EMovableAction; import jsettlers.common.movable.EMovableType; import jsettlers.common.movable.IMovable; import jsettlers.common.resources.ResourceManager; import jsettlers.graphics.image.Image; import jsettlers.graphics.map.draw.ImageProvider; /** * This is a settler image map that mapps the state of a settler to the sequence that is to be played. * <p> * The mapping is a function: (type, material, direction) => (file, sequence index, start, duration) * * @author michael */ public final class SettlerImageMap { private static final SettlerImageMapItem DEFAULT_ITEM = new SettlerImageMapItem(10, 0, 0, 1); private static SettlerImageMap instance; private final ImageProvider imageProvider = ImageProvider.getInstance(); private final SettlerImageMapItem[][][][] map; private final Pattern linePattern = Pattern.compile("\\s*([\\w\\*]+)\\s*," + "\\s*([\\w\\*]+)\\s*," + "\\s*([\\w\\*]+)\\s*," + "\\s*([\\w\\*]+)\\s*" + "=\\s*(\\d+)\\s*," + "\\s*(\\d+)\\s*," + "\\s*(\\d+)\\s*," + "\\s*(-?\\d+)\\s*"); private final int types; private final int actions; private final int materials; private final int directions; /** * Creates a new settler image map. */ private SettlerImageMap() { this.types = EMovableType.NUMBER_OF_MOVABLETYPES; this.actions = EMovableAction.values().length; this.materials = EMaterialType.NUMBER_OF_MATERIALS; this.directions = EDirection.VALUES.length; this.map = new SettlerImageMapItem[this.types][this.actions][this.materials][this.directions]; try { InputStream file = getClass().getResourceAsStream("movables.txt"); readFromFile(file); } catch (IOException e) { System.err.println("Error reading image file. " + "Settler images might not work."); } } /** * Reads the map from the given file. * * @param file * The file to read from. */ private void readFromFile(InputStream file) throws IOException { int[][][][] priorities = new int[this.types][this.actions][this.materials][this.directions]; // add pseudo entry. addEntryToMap(priorities, null, null, null, null, DEFAULT_ITEM, -1); readFromFile(file, priorities); } private void readFromFile(InputStream file, int[][][][] priorities) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(file)); String line = reader.readLine(); while (line != null) { if (!line.isEmpty() && !line.startsWith("#")) { try { addByLine(priorities, line); } catch (IllegalArgumentException e) { e.printStackTrace(); } } line = reader.readLine(); } } /** * Adds a line to the map * * @param priorities * The priority table to use. * @param line * The line. * @throws IllegalArgumentException * if the line is not correct. */ private void addByLine(int[][][][] priorities, String line) { final Matcher matcher = parseLine(line); final String typeString = matcher.group(1); final String actionString = matcher.group(2); final String materialString = matcher.group(3); final String directionString = matcher.group(4); EMovableType type = parseType(typeString); EMovableAction action = parseAction(actionString); EMaterialType material = parseMaterial(materialString); EDirection direction = parseDirection(directionString); int priority = calculatePriority(type, action, material, direction); final int fileIndex = Integer.parseInt(matcher.group(5)); final int sequence = Integer.parseInt(matcher.group(6)); final int start = Integer.parseInt(matcher.group(7)); final int duration = Integer.parseInt(matcher.group(8)); addEntryToMap(priorities, type, action, material, direction, new SettlerImageMapItem(fileIndex, sequence, start, duration), priority); } private EMovableType parseType(final String typeString) { EMovableType type; if ("*".equals(typeString)) { type = null; } else { type = EMovableType.valueOf(typeString); } return type; } private EMovableAction parseAction(final String actionString) { EMovableAction action; if ("*".equals(actionString)) { action = null; } else { action = EMovableAction.valueOf(actionString); } return action; } private EMaterialType parseMaterial(final String materialString) { EMaterialType material; if ("*".equals(materialString)) { material = null; } else { material = EMaterialType.valueOf(materialString); } return material; } private EDirection parseDirection(final String directionString) { EDirection direction; if ("*".equals(directionString)) { direction = null; } else { direction = EDirection.valueOf(directionString); } return direction; } private int calculatePriority(EMovableType type, EMovableAction action, EMaterialType material, EDirection direction) { int priority = 1;// more than 0. if (type != null) { priority += 10; } if (action != null) { priority += 100; } if (material != null) { priority += 1000; } if (direction != null) { priority += 10000; } return priority; } /** * Parses a line. * * @param line * The line. * @return The line matched against the line pattern. * @throws IllegalArgumentException * if the line is not correct. */ private Matcher parseLine(String line) { final Matcher matcher = this.linePattern.matcher(line); final boolean matches = matcher.matches(); if (!matches) { throw new IllegalArgumentException("Invalid line syntax: " + line); // ignore } return matcher; } /** * Adds an entry to the map. Overrides cells with lower priorities. * * @param priorities * The priority table to use. * @param type * @param action * @param material * @param direction * @param item * @param priority */ private void addEntryToMap(int[][][][] priorities, EMovableType type, EMovableAction action, EMaterialType material, EDirection direction, SettlerImageMapItem item, int priority) { int minType, maxType; if (type == null) { minType = 0; maxType = this.types; } else { minType = type.ordinal(); maxType = minType + 1; } int minAction, maxAction; if (action == null) { minAction = 0; maxAction = this.actions; } else { minAction = action.ordinal(); maxAction = minAction + 1; } int minMaterial, maxMaterial; if (material == null) { minMaterial = 0; maxMaterial = this.materials; } else { minMaterial = material.ordinal(); maxMaterial = minMaterial + 1; } int minDirection, maxDirection; if (direction == null) { minDirection = 0; maxDirection = this.directions; } else { minDirection = direction.ordinal(); maxDirection = minDirection + 1; } for (int typeIndex = minType; typeIndex < maxType; typeIndex++) { for (int actionIndex = minAction; actionIndex < maxAction; actionIndex++) { for (int materialIndex = minMaterial; materialIndex < maxMaterial; materialIndex++) { for (int direcitonIndex = minDirection; direcitonIndex < maxDirection; direcitonIndex++) { if (priorities[typeIndex][actionIndex][materialIndex][direcitonIndex] < priority) { this.map[typeIndex][actionIndex][materialIndex][direcitonIndex] = item; priorities[typeIndex][actionIndex][materialIndex][direcitonIndex] = priority; } } } } } } /** * Gets an image for a given settler. * * @param movable * The settler to get the image for * @return The image or an null-image. * @see SettlerImageMap#getImageForSettler(EMovableType, EMovableAction, EMaterialType, EDirection, float) */ public Image getImageForSettler(IMovable movable, float progress) { if (movable.getAction() == EMovableAction.WALKING) { progress = progress / 2; if (movable.isRightstep()) { progress += .5f; } } return getImageForSettler(movable.getMovableType(), movable.getAction(), movable.getMaterial(), movable.getDirection(), progress); } /** * Gets an image for a given settler. * * @param movableType * The type of the settler. * @param action * The action the settler is doing. * @param material * The material that is assigned to the settler. * @param direction * Its direction. * @param progress * The progress. * @return The image. */ public Image getImageForSettler(EMovableType movableType, EMovableAction action, EMaterialType material, EDirection direction, float progress) { SettlerImageMapItem item = getMapItem(movableType, action, material, direction); int duration = item.getDuration(); int imageIndex; if (duration >= 0) { imageIndex = item.getStart() + Math.min((int) (progress * duration), duration - 1); } else { imageIndex = item.getStart() + Math.max((int) (progress * duration), duration + 1); } return this.imageProvider.getSettlerSequence(item.getFile(), item.getSequenceIndex()).getImageSafe(imageIndex); } /** * Gets a map item. * * @param movableType * @param action * @param material * @param direction * @param progress * @return The item of the map at the given position. Is not null. */ private SettlerImageMapItem getMapItem(EMovableType movableType, EMovableAction action, EMaterialType material, EDirection direction) { SettlerImageMapItem item = this.map[movableType.ordinal()][action.ordinal()][material .ordinal()][direction.ordinal()]; if (item == null) { return DEFAULT_ITEM; } else { return item; } } public static SettlerImageMap getInstance() { if (instance == null) { instance = new SettlerImageMap(); } return instance; } }