/* * Ranks.java * * 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.personnel; import java.io.FileInputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.Vector; import javax.swing.JOptionPane; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import mekhq.MekHQ; import mekhq.MekHqXmlUtil; import mekhq.Version; import mekhq.gui.model.RankTableModel; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * This object will keep track of rank information. It will keep information * on a set of pre-fab rank structures and will hold info on the one chosen by the user. * It will also allow for the input of a user-created rank structure from a comma-delimited * set of names * @author Jay Lawson <jaylawson39 at yahoo.com> */ public class Ranks { // Rank Faction Codes // TODO: Major periphery realms? public static final int RS_SL = 0; public static final int RS_FS = 1; public static final int RS_FC = 2; public static final int RS_LC = 3; public static final int RS_LA = 4; public static final int RS_FWL = 5; public static final int RS_CC = 6; public static final int RS_CCWH = 7; public static final int RS_DC = 8; public static final int RS_CL = 9; public static final int RS_COM = 10; public static final int RS_WOB = 11; public static final int RS_CUSTOM = 12; public static final int RS_MOC = 13; public static final int RS_TC = 14; public static final int RS_MH = 15; public static final int RS_OA = 16; public static final int RS_FRR = 17; public static final int RS_NUM = 18; public static final int[] translateFactions = { /* 0 */ 0, /* 1 */ 1, /* 2 */ 4, /* 3 */ 5, /* 4 */ 6, /* 5 */ 8, /* 6 */ 9, /* 7 */ 12 }; // Rank Size Codes // Enlisted public static final int RE_MIN = 0; // Rank "None" public static final int RE_MAX = 20; public static final int RE_NUM = 21; // Warrant Officers public static final int RWO_MIN = 21; public static final int RWO_MAX = 30; public static final int RWO_NUM = 31; // Number that comes after RWO_MAX // Officers public static final int RO_MIN = 31; public static final int RO_MAX = 50; public static final int RO_NUM = 51; // Number that comes after RO_MAX // Total public static final int RC_NUM = 51; // Same as RO_MAX+1 // Rank Profession Codes public static final int RPROF_MW = 0; public static final int RPROF_ASF = 1; public static final int RPROF_VEE = 2; public static final int RPROF_NAVAL = 3; public static final int RPROF_INF = 4; public static final int RPROF_TECH = 5; public static final int RPROF_NUM = 6; private static Hashtable<Integer, Ranks> rankSystems; @SuppressWarnings("unused") // FIXME private static final int[] officerCut = {/*SLDF*/7,/*AFFS*/6,/*AFFC*/8,/*LCAF*/14,/*LAAF*/11,/*FWLM*/9,/*CCAF*/5,/*CCWH*/2,/*DCMD*/9,/*Clan*/3,/*COM*/2,/*WOB*/2}; public static final int RANK_BONDSMAN = -1; public static final int RANK_PRISONER = -2; private int rankSystem; private int oldRankSystem = -1; private ArrayList<Rank> ranks; public Ranks() { this(RS_SL); } public Ranks(int system) { rankSystem = system; useRankSystem(rankSystem); } public static void initializeRankSystems() { rankSystems = new Hashtable<Integer, Ranks>(); MekHQ.logMessage("Starting load of Rank Systems from XML..."); // Initialize variables. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); Document xmlDoc = null; try { FileInputStream fis = new FileInputStream("data/universe/ranks.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 ranksEle = xmlDoc.getDocumentElement(); NodeList nl = ranksEle.getChildNodes(); // Get rid of empty text nodes and adjacent text nodes... // Stupid weird parsing of XML. At least this cleans it up. ranksEle.normalize(); // Okay, lets iterate through the children, eh? for (int x = 0; x < nl.getLength(); x++) { Node wn = nl.item(x); Ranks value; if (wn.getParentNode() != ranksEle) 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("rankSystem")) { value = generateInstanceFromXML(wn, null); rankSystems.put(value.getRankSystem(), value); } } } MekHQ.logMessage("Done loading Rank Systems"); } public static Ranks getRanksFromSystem(int system) { return rankSystems.get(system); } public int getRankSystem() { return rankSystem; } public int getOldRankSystem() { return oldRankSystem; } public void setOldRankSystem(int old) { oldRankSystem = old; } public static String getRankSystemName(int system) { switch(system) { case RS_CUSTOM: return "Custom"; case RS_SL: return "Star League"; case RS_FS: return "Federated Suns"; case RS_FC: return "Federated Commonwealth"; case RS_LC: return "Lyran Commonwealth"; case RS_LA: return "Lyran Alliance"; case RS_FWL: return "Free Worlds League"; case RS_CC: return "Capellan Confederation"; case RS_CCWH: return "Capellan Confederation Warrior House"; case RS_DC: return "Draconis Combine"; case RS_CL: return "Clan"; case RS_COM: return "Comstar"; case RS_WOB: return "Word of Blake"; case RS_MOC: return "Magistry of Canopus"; case RS_MH: return "Marian Hegemony"; case RS_TC: return "Taurian Concordat"; case RS_OA: return "Outworld's Alliance"; case RS_FRR: return "Free Rasalhague Republic"; default: return "?"; } } public int getRankNumericFromNameAndProfession(int profession, String name) { while (profession != RPROF_MW && isEmptyProfession(profession)) { profession = getAlternateProfession(profession); } for(int i = 0; i < RC_NUM; i++) { Rank rank = getAllRanks().get(i); int p = profession; // Grab rank from correct profession as needed while (rank.getName(p).startsWith("--") && p != Ranks.RPROF_MW) { if (rank.getName(p).equals("--")) { p = getAlternateProfession(p); } else if (rank.getName(p).startsWith("--")) { p = getAlternateProfession(rank.getName(p)); } } // Check it... if (rank.getName(p).equals(name)) { return i; } } return 0; } public boolean isEmptyProfession(int profession) { // MechWarrior profession cannot be empty if (profession == RPROF_MW) return false; // Check the profession for (int i = 0; i < RC_NUM; i++) { // If our first Rank is an indicator of an alternate system, skip it. if (i == 0 && ranks.get(0).getName(profession).startsWith("--")) { continue; } if (!ranks.get(i).getName(profession).equals("-")) { return false; } } // It's empty... return true; } public boolean useAlternateProfession(int profession) { if (ranks.get(0).getName(profession).startsWith("--")) { return true; } return false; } public int getAlternateProfession(int profession) { return getAlternateProfession(ranks.get(0).getName(profession)); } public int getAlternateProfession(String name) { switch (name.replaceAll("--", "")) { case "MW": return RPROF_MW; case "ASF": return RPROF_ASF; case "VEE": return RPROF_VEE; case "NAVAL": return RPROF_NAVAL; case "INF": return RPROF_INF; case "TECH": return RPROF_TECH; default: return RPROF_MW; } } public ArrayList<Rank> getAllRanks() { return ranks; } private void useRankSystem(int system) { // If we've got an invalid rank system, default to Star League if(system >= rankSystems.size()) { if (rankSystems.isEmpty()) { ranks = new ArrayList<Rank>(); } else { ranks = rankSystems.get(RS_SL).getAllRanks(); } rankSystem = RS_SL; return; } ranks = rankSystems.get(system).getAllRanks(); rankSystem = system; } public void setCustomRanks(ArrayList<ArrayList<String>> customRanks, int offCut) { ranks = new ArrayList<Rank>(); for (int i = 0; i < customRanks.size(); i++) { ranks.add(new Rank(customRanks.get(i), offCut <= i, 1.0)); } rankSystem = RS_CUSTOM; } public Rank getRank(int r) { if(r >= ranks.size()) { //assign the highest rank r = ranks.size() - 1; } if (r == RANK_BONDSMAN) { // Bondsman String[] bondsmen = { "Bondsman", "Bondsman", "Bondsman", "Bondsman", "Bondsman", "Bondsman" }; return new Rank(bondsmen, false, 0.0); } if (r == RANK_PRISONER) { // Prisoners String [] prisoner = { "Prisoner", "Prisoner", "Prisoner", "Prisoner", "Prisoner", "Prisoner" }; return new Rank(prisoner, false, 0.0); } return ranks.get(r); } public int getOfficerCut() { for(int i = 0; i < ranks.size(); i++) { if(ranks.get(i).isOfficer()) { return i; } } return ranks.size() - 1; } public int getRankOrder(String rank, int profession) { if (rank.equals("Prisoner")) { return RANK_PRISONER; } if (rank.equals("Bondsman")) { return RANK_BONDSMAN; } for(int i = 0; i < ranks.size(); i++) { if(ranks.get(i).getName(profession).equals(rank)) { return i; } } return 0; } public void setRankSystem(int system) { if(system < RS_NUM) { rankSystem = system; useRankSystem(rankSystem); } } public String getRankNameList(int profession) { String rankNames = ""; int i = 0; for(Rank rank : getAllRanks()) { rankNames += rank.getName(profession); i++; if(i < getAllRanks().size()) { rankNames += ","; } } return rankNames; } //Keep this for reverse compatability in loading campaigns public void setRanksFromList(String names, int officerCut) { ArrayList<ArrayList<String>> rankNames = new ArrayList<ArrayList<String>>(); String[] rnames = names.split(","); for(String rname : rnames) { ArrayList<String> temp = new ArrayList<String>(); for (int i = 0; i < RPROF_NUM; i++) { temp.add(rname); } rankNames.add(temp); } setCustomRanks(rankNames, officerCut); } private String getRankPostTag(int rankNum) { if (rankNum == 0) return " <!-- E0 \"None\" -->"; if (rankNum < RE_NUM) return " <!-- E"+rankNum+" -->"; if (rankNum < RWO_NUM) return " <!-- WO"+(rankNum-RE_MAX)+" -->"; if (rankNum < RO_NUM) return " <!-- O"+(rankNum-RWO_MAX)+" -->"; // Yuck, we've got nada! return ""; } public void writeToXml(PrintWriter pw1, int indent) { writeToXml(pw1, indent, false); } public void writeToXml(PrintWriter pw1, int indent, boolean saveAll) { pw1.println(MekHqXmlUtil.indentStr(indent) + "<rankSystem>"); pw1.println(MekHqXmlUtil.indentStr(indent+1)+"<!-- "+getRankSystemName(rankSystem)+" -->"); pw1.println(MekHqXmlUtil.indentStr(indent+1) +"<system>" +rankSystem +"</system>"); // Only write out the ranks if we're using a custom system if (rankSystem == RS_CUSTOM || saveAll) { for(int i = 0; i < ranks.size(); i++) { Rank r = ranks.get(i); r.writeToXml(pw1, indent+1); pw1.println(getRankPostTag(i)); } } pw1.println(MekHqXmlUtil.indentStr(indent) + "</rankSystem>"); } public static Ranks generateInstanceFromXML(Node wn, Version version) { Ranks retVal = new Ranks(); boolean showMessage = false; // Dump the ranks ArrayList so we can re-use it. retVal.ranks = new ArrayList<Rank>(); try { NodeList nl = wn.getChildNodes(); for (int x=0; x<nl.getLength(); x++) { Node wn2 = nl.item(x); if (wn2.getNodeName().equalsIgnoreCase("system") || wn2.getNodeName().equalsIgnoreCase("rankSystem")) { retVal.rankSystem = Integer.parseInt(wn2.getTextContent()); // If this is an older version from before the full blown rank system with // professions, we need to translate it to match the new constants if (version != null && Version.versionCompare(version, "0.3.4-r1782")) { // Translate the rank system if (retVal.rankSystem == RankTranslator.RT_SL) { String change = (String) JOptionPane.showInputDialog( null, "Due to an error in previous versions of MekHQ this value may not be correct." + "\nPlease select the correct rank system and click OK.", "Select Correct Rank System", JOptionPane.QUESTION_MESSAGE, null, RankTranslator.oldRankNames, RankTranslator.oldRankNames[0]); retVal.rankSystem = Arrays.asList(RankTranslator.oldRankNames).indexOf(change); } retVal.oldRankSystem = retVal.rankSystem; retVal.rankSystem = Ranks.translateFactions[retVal.rankSystem]; } else if (version != null && retVal.rankSystem != RS_CUSTOM) { retVal = Ranks.getRanksFromSystem(retVal.rankSystem); } } else if (wn2.getNodeName().equalsIgnoreCase("rank")) { // If we're parsing from the XML or using custom ranks, then parse the rank sub-nodes if (retVal.oldRankSystem == RankTranslator.RT_CUSTOM) { showMessage = true; } if (version == null || retVal.rankSystem == RS_CUSTOM) { retVal.ranks.add(Rank.generateInstanceFromXML(wn2)); } else { // Otherwise... use the default ranks for this system int temp = retVal.oldRankSystem; retVal = Ranks.getRanksFromSystem(retVal.rankSystem); retVal.oldRankSystem = temp; return retVal; } } } if (showMessage) { JOptionPane.showConfirmDialog( null, "You have used a custom rank set in your campaign." + "\nYou must recreate that system for this version.", "Custom Ranks", JOptionPane.OK_CANCEL_OPTION); } } catch (Exception ex) { // Errrr, apparently either the class name was invalid... // Or the listed name doesn't exist. // Doh! MekHQ.logError(ex); } return retVal; } public Object[][] getRanksForModel() { Object[][] array = new Object[ranks.size()][RankTableModel.COL_NUM]; int i = 0; for(Rank rank : ranks) { String rating = "E"+i; if (i > RWO_MAX) { rating = "O"+(i-RWO_MAX); } else if (i > RE_MAX) { rating = "WO"+(i-RE_MAX); } array[i][RankTableModel.COL_NAME_RATE] = rating; array[i][RankTableModel.COL_NAME_MW] = rank.getNameWithLevels(RPROF_MW); array[i][RankTableModel.COL_NAME_ASF] = rank.getNameWithLevels(RPROF_ASF); array[i][RankTableModel.COL_NAME_VEE] = rank.getNameWithLevels(RPROF_VEE); array[i][RankTableModel.COL_NAME_NAVAL] = rank.getNameWithLevels(RPROF_NAVAL); array[i][RankTableModel.COL_NAME_INF] = rank.getNameWithLevels(RPROF_INF); array[i][RankTableModel.COL_NAME_TECH] = rank.getNameWithLevels(RPROF_TECH); array[i][RankTableModel.COL_OFFICER] = rank.isOfficer(); array[i][RankTableModel.COL_PAYMULT] = rank.getPayMultiplier(); i++; } return array; } public void setRanksFromModel(RankTableModel model) { ranks = new ArrayList<Rank>(); @SuppressWarnings("unchecked") // Broken java doesn't have typed vectors in the DefaultTableModel Vector<Vector<Object>> vectors = model.getDataVector(); for(Vector<Object> row : vectors) { String[] names = { (String)row.get(RankTableModel.COL_NAME_MW), (String)row.get(RankTableModel.COL_NAME_ASF), (String)row.get(RankTableModel.COL_NAME_VEE), (String)row.get(RankTableModel.COL_NAME_NAVAL), (String)row.get(RankTableModel.COL_NAME_INF), (String)row.get(RankTableModel.COL_NAME_TECH) }; Boolean officer = (Boolean)row.get(RankTableModel.COL_OFFICER); double payMult = (Double)row.get(RankTableModel.COL_PAYMULT); ranks.add(new Rank(names, officer, payMult)); } } }