/******************************************************************************* * 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.common.buildings.loader; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import jsettlers.common.buildings.EBuildingType; import jsettlers.common.buildings.OccupierPlace; import jsettlers.common.buildings.RelativeBricklayer; import jsettlers.common.buildings.jobs.IBuildingJob; import jsettlers.common.buildings.stacks.ConstructionStack; import jsettlers.common.buildings.stacks.RelativeStack; import jsettlers.common.images.EImageLinkType; import jsettlers.common.images.ImageLink; import jsettlers.common.images.OriginalImageLink; import jsettlers.common.landscape.ELandscapeType; import jsettlers.common.material.EMaterialType; import jsettlers.common.movable.EDirection; import jsettlers.common.movable.EMovableType; import jsettlers.common.movable.ESoldierClass; import jsettlers.common.position.RelativePoint; import org.xml.sax.Attributes; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; /** * This class represents a building's xml file. * * @author michael */ public class BuildingFile implements BuildingJobDataProvider { private static final String BUILDING_DTD = "building.dtd"; private static final String DATA_DIR = "buildings/"; private static final String TAG_BUILDING = "building"; private static final String TAG_JOB = "job"; private static final String TAG_STARTJOB = "startjob"; private static final String TAG_DOOR = "door"; private static final String TAG_BLOCKED = "blocked"; private static final String TAG_CONSTRUCTION_STACK = "constructionStack"; private static final String TAG_REQUEST_STACK = "requestStack"; private static final String TAG_OFFER_STACK = "offerStack"; private static final String TAG_OCCUPYER = "occupyer"; private static final String ATTR_JOBNAME = "name"; private static final String ATTR_DX = "dx"; private static final String ATTR_DY = "dy"; private static final String ATTR_MATERIAl = "material"; private static final String ATTR_BUILDREQUIRED = "buildrequired"; private static final String TAG_WORKCENTER = "workcenter"; private static final String TAG_FLAG = "flag"; private static final String TAG_BRICKLAYER = "bricklayer"; private static final String ATTR_DIRECTION = "direction"; private static final String TAG_BUILDMARK = "buildmark"; private static final String TAG_IMAGE = "image"; private static final String TAG_GROUNDTYE = "ground"; private final ArrayList<RelativePoint> blocked = new ArrayList<RelativePoint>(); private final ArrayList<RelativePoint> protectedTiles = new ArrayList<RelativePoint>(); private final Hashtable<String, JobElementWrapper> jobElements = new Hashtable<>(); private String startJobName = ""; private RelativePoint door = new RelativePoint(0, 0); private IBuildingJob startJob = null; private EMovableType workerType; private ArrayList<ConstructionStack> constructionStacks = new ArrayList<>(); private ArrayList<RelativeStack> requestStacks = new ArrayList<>(); private ArrayList<RelativeStack> offerStacks = new ArrayList<>(); private ArrayList<RelativeBricklayer> bricklayers = new ArrayList<>(); private int workradius; private boolean mine; private RelativePoint workCenter = new RelativePoint(0, 0); private RelativePoint flag = new RelativePoint(0, 0); private ArrayList<RelativePoint> buildmarks = new ArrayList<RelativePoint>(); private ImageLink guiimage = new OriginalImageLink(EImageLinkType.GUI, 1, 0, 0); private ArrayList<ImageLink> images = new ArrayList<ImageLink>(); private ArrayList<ImageLink> buildImages = new ArrayList<ImageLink>(); private ArrayList<ELandscapeType> groundtypes = new ArrayList<ELandscapeType>(); private ArrayList<OccupierPlace> occupyerplaces = new ArrayList<OccupierPlace>(); private short viewdistance = 0; private final String buildingName; public BuildingFile(String buildingName) { this.buildingName = buildingName; try { XMLReader xr = XMLReaderFactory.createXMLReader(); xr.setContentHandler(new SaxHandler()); xr.setEntityResolver(new EntityResolver() { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if (systemId.contains(BUILDING_DTD)) { return new InputSource(EBuildingType.class.getResourceAsStream(BUILDING_DTD)); } else { return null; } } }); InputStream stream = EBuildingType.class.getResourceAsStream(String.format("%s.xml", buildingName.toLowerCase())); xr.parse(new InputSource(stream)); } catch (Exception e) { System.err.println("Error loading building file for " + buildingName + ":" + e.getMessage()); loadDefault(); } } private class SaxHandler extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { String tagName = qName; if (TAG_BUILDING.equals(tagName)) { readAttributes(attributes); } else if (TAG_JOB.equals(tagName)) { String name = attributes.getValue(ATTR_JOBNAME); jobElements.put(name, new JobElementWrapper(attributes)); } else if (TAG_STARTJOB.equals(tagName)) { startJobName = attributes.getValue(ATTR_JOBNAME); } else if (TAG_DOOR.equals(tagName)) { door = readRelativeTile(attributes); } else if (TAG_WORKCENTER.equals(tagName)) { workCenter = readRelativeTile(attributes); } else if (TAG_FLAG.equals(tagName)) { flag = readRelativeTile(attributes); } else if (TAG_BLOCKED.equals(tagName)) { RelativePoint point = readRelativeTile(attributes); if ("true".equals(attributes.getValue("block"))) { blocked.add(point); } protectedTiles.add(point); } else if (TAG_CONSTRUCTION_STACK.equals(tagName)) { readConstructionStack(attributes); } else if (TAG_REQUEST_STACK.equals(tagName)) { readAndAddRelativeStack(attributes, requestStacks); } else if (TAG_OFFER_STACK.equals(tagName)) { readAndAddRelativeStack(attributes, offerStacks); } else if (TAG_BRICKLAYER.equals(tagName)) { readRelativeBricklayer(attributes); } else if (TAG_IMAGE.equals(tagName)) { readImageLink(attributes); } else if (TAG_BUILDMARK.equals(tagName)) { buildmarks.add(readRelativeTile(attributes)); } else if (TAG_GROUNDTYE.equals(tagName)) { groundtypes.add(ELandscapeType.valueOf(attributes.getValue("groundtype"))); } else if (TAG_OCCUPYER.equals(tagName)) { addOccupyer(attributes); } } } private void loadDefault() { blocked.add(new RelativePoint(0, 0)); protectedTiles.add(new RelativePoint(0, 0)); System.err.println("Building file defect: " + buildingName); } private void addOccupyer(Attributes attributes) { try { int x = parseOptionalInteger(attributes.getValue("offsetX")); int y = parseOptionalInteger(attributes.getValue("offsetY")); ESoldierClass type = ESoldierClass.valueOf(attributes.getValue("type")); RelativePoint position = new RelativePoint(Short.parseShort(attributes.getValue("soldierX")), Short.parseShort(attributes .getValue("soldierY"))); OccupierPlace place = new OccupierPlace(x, y, type, position, "true".equals(attributes.getValue("looksRight"))); occupyerplaces.add(place); } catch (NumberFormatException e) { System.err.println("Warning: illegal number " + "for occupyer x/y attribute, in definiton for " + buildingName); } catch (IllegalArgumentException e) { System.err.println("Illegal occupyer position name in " + buildingName); } } /** * If value != null, the value is parsed. Otherwise, 0 is returned. * * @param value * @return * @throws NumberFormatException */ private int parseOptionalInteger(String value) throws NumberFormatException { if (value != null) { return Integer.parseInt(value); } else { return 0; } } private void readImageLink(Attributes attributes) { try { ImageLink imageLink = getImageFromAttributes(attributes); String forState = attributes.getValue("for"); if ("GUI".equals(forState)) { guiimage = imageLink; } else if ("BUILD".equals(forState)) { buildImages.add(imageLink); } else { images.add(imageLink); } } catch (NumberFormatException e) { System.err.println("Warning: illegal number " + "for image link attribute, in definiton for " + buildingName); } catch (IllegalArgumentException e) { System.err.println("Illegal image link name in " + buildingName); } } public static ImageLink getImageFromAttributes(Attributes attributes) { ImageLink imageLink; if (attributes.getIndex("name") < 0) { imageLink = getOriginalImageLink(attributes); } else { String name = attributes.getValue("name"); int image = Integer.parseInt(attributes.getValue("image")); imageLink = ImageLink.fromName(name, image); } return imageLink; } private static OriginalImageLink getOriginalImageLink(Attributes attributes) { int file = Integer.parseInt(attributes.getValue("file")); int sequence = Integer.parseInt(attributes.getValue("sequence")); String imageStr = attributes.getValue("image"); int image = imageStr != null ? Integer.parseInt(imageStr) : 0; EImageLinkType type = EImageLinkType.valueOf(attributes.getValue("type")); OriginalImageLink imageLink = new OriginalImageLink(type, file, sequence, image); return imageLink; } private void readRelativeBricklayer(Attributes attributes) { try { int dx = Integer.parseInt(attributes.getValue(ATTR_DX)); int dy = Integer.parseInt(attributes.getValue(ATTR_DY)); EDirection direction = EDirection.valueOf(attributes.getValue(ATTR_DIRECTION)); bricklayers.add(new RelativeBricklayer(dx, dy, direction)); } catch (NumberFormatException e) { System.err.println("Warning: illegal number for stack attribute, in definiton for " + buildingName); } catch (IllegalArgumentException e) { System.err.println("Illegal direction name in " + buildingName); } } private RelativePoint readRelativeTile(Attributes attributes) { try { int dx = Integer.parseInt(attributes.getValue(ATTR_DX)); int dy = Integer.parseInt(attributes.getValue(ATTR_DY)); return new RelativePoint(dx, dy); } catch (NumberFormatException e) { System.err.println("Warning: illegal number " + "for relative tile attribute, in definiton for " + buildingName); return new RelativePoint(0, 0); } } private void readAndAddRelativeStack(Attributes attributes, ArrayList<RelativeStack> stacks) { try { int dx = Integer.parseInt(attributes.getValue(ATTR_DX)); int dy = Integer.parseInt(attributes.getValue(ATTR_DY)); EMaterialType type = EMaterialType.valueOf(attributes.getValue(ATTR_MATERIAl)); stacks.add(new RelativeStack(dx, dy, type)); } catch (NumberFormatException e) { System.err.println("Warning: illegal number " + "for stack attribute, in definiton for " + buildingName); } catch (IllegalArgumentException e) { System.err.println("Illegal material name in " + buildingName); } } private void readConstructionStack(Attributes attributes) { try { int dx = Integer.parseInt(attributes.getValue(ATTR_DX)); int dy = Integer.parseInt(attributes.getValue(ATTR_DY)); EMaterialType type = EMaterialType.valueOf(attributes.getValue(ATTR_MATERIAl)); short requiredForBuild = Short.parseShort(attributes.getValue(ATTR_BUILDREQUIRED)); if (requiredForBuild <= 0) { throw new NumberFormatException("RequiredForBuild attribute needs to be an integer > 0"); } constructionStacks.add(new ConstructionStack(dx, dy, type, requiredForBuild)); } catch (NumberFormatException e) { System.err.println("Warning: illegal number " + "for stack attribute, in definiton for " + buildingName); } catch (IllegalArgumentException e) { System.err.println("Illegal material name in " + buildingName); } } /** * Read from a building tag * * @param attributes */ private void readAttributes(Attributes attributes) { String workerName = attributes.getValue("worker"); if (workerName == null || workerName.isEmpty()) { this.workerType = null; } else { try { this.workerType = EMovableType.valueOf(workerName); } catch (IllegalArgumentException e) { System.err.println("Illegal worker name: " + workerName); this.workerType = EMovableType.BEARER; } } String workradius = attributes.getValue("workradius"); if (workradius != null && workradius.matches("\\d+")) { this.workradius = Integer.parseInt(workradius); } String viewdistance = attributes.getValue("viewdistance"); if (viewdistance != null && viewdistance.matches("\\d+")) { this.viewdistance = Short.parseShort(viewdistance); } this.mine = Boolean.valueOf(attributes.getValue("mine")); } public IBuildingJob getStartJob() { if (startJob == null) { try { if (startJobName == null || startJobName.isEmpty()) { startJob = SimpleBuildingJob.createFallback(); } else { startJob = SimpleBuildingJob.createLinkedJobs(this, startJobName); } } catch (Exception e) { System.err.println("Error while creating job list for " + buildingName + ", using fallback. Message: " + e); e.printStackTrace(); startJob = SimpleBuildingJob.createFallback(); } } return startJob; } public RelativePoint getDoor() { return door; } @Override public BuildingJobData getJobData(String name) { return jobElements.get(name); } public EMovableType getWorkerType() { return workerType; } public RelativePoint[] getProtectedTiles() { return protectedTiles.toArray(new RelativePoint[protectedTiles.size()]); } public RelativePoint[] getBlockedTiles() { return blocked.toArray(new RelativePoint[blocked.size()]); } public ConstructionStack[] getConstructionRequiredStacks() { return constructionStacks.toArray(new ConstructionStack[constructionStacks.size()]); } public RelativeStack[] getRequestStacks() { return requestStacks.toArray(new RelativeStack[requestStacks.size()]); } public RelativeStack[] getOfferStacks() { return offerStacks.toArray(new RelativeStack[offerStacks.size()]); } public RelativeBricklayer[] getBricklayers() { return bricklayers.toArray(new RelativeBricklayer[bricklayers.size()]); } public short getWorkradius() { return (short) workradius; } public boolean isMine() { return mine; } public RelativePoint getWorkcenter() { return workCenter; } public RelativePoint getFlag() { return flag; } public ImageLink[] getImages() { return images.toArray(new ImageLink[images.size()]); } public ImageLink[] getBuildImages() { return buildImages.toArray(new ImageLink[buildImages.size()]); } public ImageLink getGuiImage() { return guiimage; } public RelativePoint[] getBuildmarks() { return buildmarks.toArray(new RelativePoint[buildmarks.size()]); } public List<ELandscapeType> getGroundtypes() { return groundtypes; } public short getViewdistance() { return viewdistance; } public OccupierPlace[] getOccupyerPlaces() { return occupyerplaces.toArray(new OccupierPlace[occupyerplaces.size()]); } }