/* * Faction.java * * Copyright (C) 2009-2016 MegaMek team * Copyright (c) 2009 Jay Lawson <jaylawson39 at yahoo.com>. All rights reserved. * * This file is part of MekHQ. * * MekHQ is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MekHQ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MekHQ. If not, see <http://www.gnu.org/licenses/>. */ package mekhq.campaign.universe; import java.awt.Color; import java.io.FileInputStream; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.joda.time.DateTime; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import megamek.common.EquipmentType; import mekhq.MekHQ; import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.parts.Part; /** * * @author Jay Lawson <jaylawson39 at yahoo.com> */ public class Faction { private static Map<String,Faction> factions; private static Map<Integer,Faction> factionIdMap; public static String[] choosableFactionCodes = {"MERC","CC","DC","FS","FWL","LA","FC","ROS","CS","WOB","FRR","SIC","MOC","MH","OA","TC","CDS","CGB","CHH","CJF","CNC","CSJ","CSV","CW","TH","RWR"}; private String shortname; private String fullname; private String[] altNames; private Color color; private String nameGenerator; private String[] startingPlanet; private int[] eraMods; private Integer id; private Set<Tag> tags; // Start and end years (inclusive) private int start; private int end; public Faction() { this("???", "Unknown"); } public Faction(String sname, String fname) { shortname = sname; fullname = fname; nameGenerator = "General"; color = Color.LIGHT_GRAY; startingPlanet = new String[]{"Terra","Terra","Terra","Terra","Terra","Terra","Terra","Terra","Terra"}; altNames = new String[]{"","","","","","","","",""}; eraMods = new int[]{0,0,0,0,0,0,0,0,0}; tags = EnumSet.noneOf(Faction.Tag.class); start = 0; end = 9999; } public String getShortName() { return shortname; } public String getFullName(int era) { String alt = ""; if(altNames.length > era) { alt = altNames[era]; } if(alt.trim().length() == 0) { return fullname; } else { return alt; } } public Color getColor() { return color; } public boolean isClan() { return is(Tag.CLAN); } public boolean isPeriphery() { return is(Tag.PERIPHERY); } public String getNameGenerator() { return nameGenerator; } public String getStartingPlanet(int era) { if(startingPlanet.length > era) { return startingPlanet[era]; } else if(startingPlanet.length > 0) { return startingPlanet[startingPlanet.length-1]; } return "Terra"; } public int getEraMod(int era) { if(eraMods.length > era) { return eraMods[era]; } return 0; } public int getTechMod(Part part, Campaign campaign) { int currentYear = campaign.getCalendar().get(Calendar.YEAR); //TODO: This seems hacky - we shouldn't hardcode in universe details //like this int factionMod = 0; if (part.getTechBase() == Part.T_CLAN && !isClan()) { // Availability of clan tech for IS if (currentYear<3050) // Impossible to buy before clan invasion factionMod = 12; else if (currentYear<=3052) // Between begining of clan invasiuon and tukayyid, very very hard to buy factionMod = 5; else if (currentYear<=3060) // Between tukayyid and great refusal, very hard to buy factionMod = 4; else // After great refusal, hard to buy factionMod = 3; } if (part.getTechBase() == Part.T_IS && isPeriphery()) { // Availability of high tech rating equipment in low tech areas (periphery) switch (part.getTechRating()) { case(EquipmentType.RATING_E) : factionMod += 1; break; case(EquipmentType.RATING_F) : factionMod += 2; break; } } return factionMod; } public boolean is(Faction.Tag tag) { return tags.contains(tag); } public boolean validIn(int year) { return (year >= start) && (year <= end); } public boolean validIn(DateTime time) { return validIn(time.getYear()); } public Integer getId() { return id; } public static Collection<Faction> getFactions() { return factions.values(); } public static Collection<String> getFactionList() { return new ArrayList<>(factions.keySet()); } public static Faction getFaction(String sname) { return factions.get(sname); } public static Faction getFactionFromFullName(String fname, int year) { return getFactionFromFullNameAndEra(fname, Era.getEra(year)); } public static Faction getFactionFromFullNameAndEra(String fname, int era) { Faction faction = null; for (Faction f : factions.values()) { if (f.getFullName(era).equals(fname)) { faction = f; break; } } return faction; } public static String getFactionCode(int faction) { Faction f = factionIdMap.get(faction); return (null != f) ? f.getShortName() : "IND"; //$NON-NLS-1$ } public static Faction getFactionFromXML(Node wn) throws DOMException, ParseException { Faction retVal = new Faction(); NodeList nl = wn.getChildNodes(); for (int x=0; x<nl.getLength(); x++) { Node wn2 = nl.item(x); if (wn2.getNodeName().equalsIgnoreCase("shortname")) { retVal.shortname = wn2.getTextContent(); } else if (wn2.getNodeName().equalsIgnoreCase("fullname")) { retVal.fullname = wn2.getTextContent(); } else if (wn2.getNodeName().equalsIgnoreCase("nameGenerator")) { retVal.nameGenerator = wn2.getTextContent(); } else if (wn2.getNodeName().equalsIgnoreCase("clan")) { if (wn2.getTextContent().equalsIgnoreCase("true")) { retVal.tags.add(Tag.CLAN); } else { retVal.tags.remove(Tag.CLAN); } } else if (wn2.getNodeName().equalsIgnoreCase("periphery")) { if (wn2.getTextContent().equalsIgnoreCase("true")) { retVal.tags.add(Tag.PERIPHERY); } else { retVal.tags.remove(Tag.PERIPHERY); } } else if (wn2.getNodeName().equalsIgnoreCase("startingPlanet")) { retVal.startingPlanet = wn2.getTextContent().split(",", -2); } else if (wn2.getNodeName().equalsIgnoreCase("altNames")) { retVal.altNames = wn2.getTextContent().split(",", -2); } else if (wn2.getNodeName().equalsIgnoreCase("eraMods")) { String[] values = wn2.getTextContent().split(",", -2); for(int i = 0; i < values.length; i++) { retVal.eraMods[i] = Integer.parseInt(values[i]); } } else if (wn2.getNodeName().equalsIgnoreCase("colorRGB")) { String[] values = wn2.getTextContent().split(","); if(values.length == 3) { int colorRed = Integer.parseInt(values[0]); int colorGreen = Integer.parseInt(values[1]); int colorBlue = Integer.parseInt(values[2]); retVal.color = new Color(colorRed, colorGreen, colorBlue); } } else if(wn2.getNodeName().equalsIgnoreCase("id")) { retVal.id = Integer.valueOf(wn2.getTextContent()); } else if(wn2.getNodeName().equalsIgnoreCase("start")) { retVal.start = Integer.valueOf(wn2.getTextContent()); } else if(wn2.getNodeName().equalsIgnoreCase("end")) { retVal.end = Integer.valueOf(wn2.getTextContent()); } else if(wn2.getNodeName().equalsIgnoreCase("tags")) { Arrays.stream(wn2.getTextContent().split(",")).map(tag -> tag.toUpperCase(Locale.ROOT)) .map(Tag::valueOf).forEach(tag -> retVal.tags.add(tag)); } } if(retVal.altNames.length < Era.E_NUM) { MekHQ.logMessage(retVal.fullname + " faction did not have a long enough altNames vector"); } if(retVal.eraMods.length < Era.E_NUM) { MekHQ.logMessage(retVal.fullname + " faction did not have a long enough eraMods vector"); } if(!retVal.is(Tag.PIRATE) && !retVal.is(Tag.MERC) && !retVal.is(Tag.TRADER)) { // Planet checks if(retVal.startingPlanet.length < Era.E_NUM) { MekHQ.logMessage(retVal.fullname + " faction did not have a long enough startingPlanet vector"); } } return retVal; } public static void generateFactions() throws DOMException, ParseException { MekHQ.logMessage("Starting load of faction data from XML..."); // Initialize variables. factions = new HashMap<>(); factionIdMap = new HashMap<>(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); Document xmlDoc = null; try(FileInputStream fis = new FileInputStream("data/universe/factions.xml")) { // Using factory get an instance of document builder DocumentBuilder db = dbf.newDocumentBuilder(); // Parse using builder to get DOM representation of the XML file xmlDoc = db.parse(fis); } catch (Exception ex) { MekHQ.logError(ex); } Element factionEle = xmlDoc.getDocumentElement(); NodeList nl = factionEle.getChildNodes(); // Get rid of empty text nodes and adjacent text nodes... // Stupid weird parsing of XML. At least this cleans it up. factionEle.normalize(); // Okay, lets iterate through the children, eh? for (int x = 0; x < nl.getLength(); x++) { Node wn = nl.item(x); if (wn.getParentNode() != factionEle) continue; int xc = wn.getNodeType(); if (xc == Node.ELEMENT_NODE) { // This is what we really care about. // All the meat of our document is in this node type, at this // level. // Okay, so what element is it? String xn = wn.getNodeName(); if (xn.equalsIgnoreCase("faction")) { Faction f = getFactionFromXML(wn); if(!factions.containsKey(f.getShortName())) { factions.put(f.getShortName(), f); if(null != f.getId()) { if(!factionIdMap.containsKey(f.getId())) { factionIdMap.put(f.getId(), f); } else { MekHQ.logError(String.format("Faction id \"%d\" already used for faction %s, can't re-use it for %s", f.getId().intValue(), factionIdMap.get(f.getId()).getFullName(0), f.getFullName(0))); } } } else { MekHQ.logError(String.format("Faction code \"%s\" already used for faction %s, can't re-use it for %s", f.getShortName(), factions.get(f.getShortName()).getFullName(0), f.getFullName(0))); } } else if (xn.equalsIgnoreCase("choosableFactionCodes")) { choosableFactionCodes = wn.getTextContent().split(","); } } } MekHQ.logMessage("Loaded a total of " + factions.size() + " factions"); } /** @return Sorted list of faction names as one string */ public static String getFactionNames(Collection<Faction> factions, int era) { if( null == factions ) { return "-"; //$NON-NLS-1$ } List<String> factionNames = new ArrayList<>(factions.size()); for(Faction f : factions) { factionNames.add(f.getFullName(era)); } Collections.sort(factionNames); return Utilities.combineString(factionNames, "/"); //$NON-NLS-1$ } public static enum Tag { /** Inner sphere */ IS, PERIPHERY, CLAN, /** A bunch of dirty pirates */ PIRATE, /** Major mercenary bands */ MERC, /** Major trading company */ TRADER, /** Faction is limited to a single star system, or potentially just a part of a planet */ MINOR, /** Faction is rebelling against the superior ("parent") faction */ REBEL, /** Faction isn't overtly acting on the political/military scale; think ComStar before clan invasion */ INACTIVE, /** Faction represents empty space */ ABANDONED, /** Faction represents a lack of unified government */ CHAOS, /** Faction is campaign-specific, generated on the fly */ GENERATED, /** Faction is hidden from view */ HIDDEN } }