Java tutorial
/* * Utilities.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; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.StringTokenizer; import java.util.Vector; import javax.swing.JTable; import javax.swing.table.TableModel; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDateTime; import megamek.common.Aero; import megamek.common.AmmoType; import megamek.common.BattleArmor; import megamek.common.Compute; import megamek.common.ConvFighter; import megamek.common.Coords; import megamek.common.Crew; import megamek.common.Entity; import megamek.common.Infantry; import megamek.common.Jumpship; import megamek.common.Mech; import megamek.common.MechSummary; import megamek.common.MechSummaryCache; import megamek.common.Mounted; import megamek.common.Protomech; import megamek.common.SmallCraft; import megamek.common.Tank; import megamek.common.TechConstants; import megamek.common.VTOL; import megamek.common.options.IOption; import mekhq.campaign.Campaign; import mekhq.campaign.CampaignOptions; import mekhq.campaign.parts.Part; import mekhq.campaign.parts.equipment.AmmoBin; import mekhq.campaign.parts.equipment.EquipmentPart; import mekhq.campaign.parts.equipment.MissingAmmoBin; import mekhq.campaign.parts.equipment.MissingEquipmentPart; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.unit.CrewType; import mekhq.campaign.unit.Unit; /** * * @author Jay Lawson <jaylawson39 at yahoo.com> */ public class Utilities { private static final int MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24; // A couple of arrays for use in the getLevelName() method private static int[] arabicNumbers = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; private static String[] romanNumerals = "M,CM,D,CD,C,XC,L,XL,X,IX,V,IV,I".split(","); //$NON-NLS-1$ //$NON-NLS-2$ public static int roll3d6() { Vector<Integer> rolls = new Vector<Integer>(); rolls.add(Compute.d6()); rolls.add(Compute.d6()); rolls.add(Compute.d6()); Collections.sort(rolls); return (rolls.elementAt(0) + rolls.elementAt(1)); } /* * Roll a certain number of dice with a certain number of faces */ public static int dice(int num, int faces) { int result = 0; // Roll however many dice as necessary for (int i = 0; i < num; i++) { result += Compute.randomInt(faces) + 1; } return result; } /** * Get a random element out of a collection, with equal probability. * <p> * This is the same as calling the following code, only plays nicely with * all collections (including ones like Set which don't implement RandomAccess) * and deals gracefully with empty collections. * <pre> * collection.get(Compute.randomInt(collection.size()); * </pre> * * @return <i>null</i> if the collection itself is null or empty; * can return <i>null</i> if the collection contains <i>null</i> items. * */ public static <T> T getRandomItem(Collection<? extends T> collection) { if ((null == collection) || collection.isEmpty()) { return null; } int index = Compute.randomInt(collection.size()); Iterator<? extends T> iterator = collection.iterator(); for (int i = 0; i < index; ++i) { iterator.next(); } return iterator.next(); } /** * Get a random element out of a list, with equal probability. * <p> * This is the same as calling the following code, * only deals gracefully with empty lists. * <pre> * list.get(Compute.randomInt(list.size()); * </pre> * * @return <i>null</i> if the list itself is null or empty; * can return <i>null</i> if the list contains <i>null</i> items. * */ public static <T> T getRandomItem(List<? extends T> list) { if ((null == list) || list.isEmpty()) { return null; } int index = Compute.randomInt(list.size()); return list.get(index); } /** * @return linear interpolation value between min and max */ public static double lerp(double min, double max, double f) { // The order of operations is important here, to not lose precision return min * (1.0 - f) + max * f; } /** * @return linear interpolation value between min and max, rounded to the nearest integer */ public static int lerp(int min, int max, double f) { // The order of operations is important here, to not lose precision return (int) Math.round(min * (1.0 - f) + max * f); } /** * @return linear interpolation value between min and max, rounded to the nearest coordinate * <p> * For theory behind the method used, see: http://www.redblobgames.com/grids/hexagons/ */ public static Coords lerp(Coords min, Coords max, double f) { int minX = min.getX(); int minZ = min.getY() - (min.getX() - (min.getX() & 1)) / 2; int minY = -minX - minZ; int maxX = max.getX(); int maxZ = max.getY() - (max.getX() - (max.getX() & 1)) / 2; int maxY = -maxX - maxZ; double lerpX = lerp((double) minX, (double) maxX, f); double lerpY = lerp((double) minY, (double) maxY, f); double lerpZ = lerp((double) minZ, (double) maxZ, f); int resultX = (int) Math.round(lerpX); int resultY = (int) Math.round(lerpY); int resultZ = (int) Math.round(lerpZ); double diffX = Math.abs(resultX * 1.0 - lerpX); double diffY = Math.abs(resultY * 1.0 - lerpY); double diffZ = Math.abs(resultZ * 1.0 - lerpZ); if ((diffX > diffY) && (diffX > diffZ)) { resultX = -resultY - resultZ; } else if (diffY > diffZ) { resultY = -resultX - resultZ; } else { resultZ = -resultX - resultY; } return new Coords(resultX, resultZ + (resultX - (resultX & 1)) / 2); } /** * The method is returns the same as a call to the following code: * <pre>T result = (null != getFirst()) ? getFirst() : getSecond();</pre> * ... with the major difference that getFirst() and getSecond() get evaluated exactly once. * <p> * This means that it doesn't matter if getFirst() is relatively expensive to evaluate * or has side effects. It also means that getSecond() gets evaluated <i>regardless</i> if * it is needed or not. Since Java guarantees the order of evaluation for arguments to be * the same as the order in which they appear (JSR 15.7.4), this makes it more suitable * for re-playable procedural generation and similar method calls with side effects. * * @return the first argument if it's not <i>null</i>, else the second argument */ public static <T> T nonNull(T first, T second) { return (null != first) ? first : second; } /** * For details and caveats, see the two-argument method. * * @return the first non-<i>null</i> argument, else <i>null</i> if all are <i>null</i> */ @SafeVarargs public static <T> T nonNull(T first, T second, T... others) { if (null != first) { return first; } if (null != second) { return second; } T result = others[0]; int index = 1; while ((null == result) && (index < others.length)) { result = others[index]; ++index; } return result; } public static ArrayList<AmmoType> getMunitionsFor(Entity entity, AmmoType cur_atype, int techLvl) { ArrayList<AmmoType> atypes = new ArrayList<AmmoType>(); for (AmmoType atype : AmmoType.getMunitionsFor(cur_atype.getAmmoType())) { //this is an abbreviated version of setupMunitions in the CustomMechDialog //TODO: clan/IS limitations? if ((entity instanceof Aero) && !((atype.getAmmoType() == AmmoType.T_MML) || (atype.getAmmoType() == AmmoType.T_ATM) || (atype.getAmmoType() == AmmoType.T_NARC) || (atype.getAmmoType() == AmmoType.T_AC_LBX))) { continue; } int lvl = atype.getTechLevel(entity.getTechLevelYear()); if (lvl < 0) { lvl = 0; } if (techLvl < Utilities.getSimpleTechLevel(lvl)) { continue; } if (TechConstants.isClan(cur_atype.getTechLevel(entity.getTechLevelYear())) != TechConstants .isClan(lvl)) { continue; } // Only Protos can use Proto-specific ammo if (atype.hasFlag(AmmoType.F_PROTOMECH) && !(entity instanceof Protomech)) { continue; } // When dealing with machine guns, Protos can only // use proto-specific machine gun ammo if ((entity instanceof Protomech) && atype.hasFlag(AmmoType.F_MG) && !atype.hasFlag(AmmoType.F_PROTOMECH)) { continue; } // Battle Armor ammo can't be selected at all. // All other ammo types need to match on rack size and tech. if ((atype.getRackSize() == cur_atype.getRackSize()) && (atype.hasFlag(AmmoType.F_BATTLEARMOR) == cur_atype.hasFlag(AmmoType.F_BATTLEARMOR)) && (atype.hasFlag(AmmoType.F_ENCUMBERING) == cur_atype.hasFlag(AmmoType.F_ENCUMBERING)) && (atype.getTonnage(entity) == cur_atype.getTonnage(entity))) { atypes.add(atype); } } return atypes; } public static boolean compareMounted(Mounted a, Mounted b) { if (!a.getType().equals(b.getType())) return false; if (!a.getClass().equals(b.getClass())) return false; if (!a.getName().equals(b.getName())) return false; if (a.getLocation() != b.getLocation()) return false; return true; } public static String getCurrencyString(long value) { NumberFormat numberFormat = DecimalFormat.getIntegerInstance(); String text = numberFormat.format(value) + " C-Bills"; return text; } /** * Returns the last file modified in a directory and all subdirectories * that conforms to a FilenameFilter * @param dir * @param filter * @return */ public static File lastFileModified(String dir, FilenameFilter filter) { File fl = new File(dir); File[] files = fl.listFiles(filter); long lastMod = Long.MIN_VALUE; File choice = null; for (File file : files) { if (file.lastModified() > lastMod) { choice = file; lastMod = file.lastModified(); } } //ok now we need to recursively search any subdirectories, so see if they //contain more recent files files = fl.listFiles(); for (File file : files) { if (!file.isDirectory()) { continue; } File subFile = lastFileModified(file.getPath(), filter); if (null != subFile && subFile.lastModified() > lastMod) { choice = subFile; lastMod = subFile.lastModified(); } } return choice; } public static File[] getAllFiles(String dir, FilenameFilter filter) { File fl = new File(dir); File[] files = fl.listFiles(filter); return files; } public static ArrayList<String> getAllVariants(Entity en, int year, CampaignOptions options) { ArrayList<String> variants = new ArrayList<String>(); for (MechSummary summary : MechSummaryCache.getInstance().getAllMechs()) { // If this isn't the same chassis, is our current unit, or is a different weight we continue if (!en.getChassis().equalsIgnoreCase(summary.getChassis()) || en.getModel().equalsIgnoreCase(summary.getModel()) || summary.getTons() != en.getWeight()) { continue; } // If we only allow canon units and this isn't canon we continue if (!summary.isCanon() && options.allowCanonOnly()) { continue; } // If we're limiting by year and aren't to this unit's year yet we continue if (options.limitByYear() && summary.getYear() > year) { continue; } // If the tech level doesn't meet the game's tech level we continue if (options.getTechLevel() < Utilities.getSimpleTechLevel(summary.getType())) { continue; } // Otherwise, we can offer it for selection variants.add(summary.getModel()); } return variants; } public static int generateExpLevel(int bonus) { int roll = Compute.d6(2) + bonus; if (roll < 2) { return SkillType.EXP_ULTRA_GREEN; } if (roll < 6) { return SkillType.EXP_GREEN; } else if (roll < 10) { return SkillType.EXP_REGULAR; } else if (roll < 12) { return SkillType.EXP_VETERAN; } else { return SkillType.EXP_ELITE; } } public static Person findCommander(Entity entity, ArrayList<Person> vesselCrew, ArrayList<Person> gunners, ArrayList<Person> drivers, Person navigator) { //take first by rank //if rank is tied, take gunners over drivers //if two of the same type are tie rank, take the first one int bestRank = -1; Person commander = null; for (Person p : vesselCrew) { if (null != p && p.getRankNumeric() > bestRank) { commander = p; bestRank = p.getRankNumeric(); } } for (Person p : gunners) { if (p.getRankNumeric() > bestRank) { commander = p; bestRank = p.getRankNumeric(); } } for (Person p : drivers) { if (null != p && p.getRankNumeric() > bestRank) { commander = p; bestRank = p.getRankNumeric(); } } if (navigator != null) { if (null != navigator && navigator.getRankNumeric() > bestRank) { commander = navigator; bestRank = navigator.getRankNumeric(); } } return commander; } /* * Simple utility function to take a specified number and randomize it a little bit * roll 1d6 results in: * 1: target - 2 * 2: target - 1 * 3 & 4: target * 5: target + 1 * 6: target + 2 */ public static int randomSkillFromTarget(int target) { int dice = Compute.d6(); if (dice == 1) { target -= 2; } else if (dice == 2) { target -= 1; } else if (dice == 5) { target += 1; } else if (dice == 6) { target += 2; } return Math.max(target, 0); } /* * If an infantry platoon or vehicle crew took damage, perform the personnel injuries */ public static ArrayList<Person> doCrewInjuries(Entity e, Campaign c, ArrayList<Person> newCrew) { int casualties = 0; if (null != e && e instanceof Infantry) { e.applyDamage(); casualties = newCrew.size() - ((Infantry) e).getShootingStrength(); for (Person p : newCrew) { for (int i = 0; i < casualties; i++) { if (Compute.d6(2) >= 7) { int hits = c.getCampaignOptions().getMinimumHitsForVees(); if (c.getCampaignOptions().useAdvancedMedical() || c.getCampaignOptions().useRandomHitsForVees()) { int range = 6 - hits; hits = hits + Compute.randomInt(range); } p.setHits(hits); } else { p.setHits(6); } } } } return newCrew; } public static boolean isDeadCrew(Entity e) { if (Compute.getFullCrewSize(e) == 0 || e.getCrew().isDead()) { return true; } return false; } public static Map<CrewType, Collection<Person>> genRandomCrewWithCombinedSkill(Campaign c, Unit u) { Objects.requireNonNull(c); Objects.requireNonNull(u); Objects.requireNonNull(u.getEntity(), "Unit needs to have a valid Entity attached"); Crew oldCrew = u.getEntity().getCrew(); String commanderName = oldCrew.getName(); int averageGunnery = 0; int averagePiloting = 0; List<Person> drivers = new ArrayList<Person>(); List<Person> gunners = new ArrayList<Person>(); List<Person> vesselCrew = new ArrayList<Person>(); Person navigator = null; int totalGunnery = 0; int totalPiloting = 0; drivers.clear(); gunners.clear(); vesselCrew.clear(); navigator = null; // If the entire crew is dead, we don't want to generate them. // Actually, we do because they might not be truly dead - this will be the case for BA for example // Also, the user may choose to GM make them un-dead in the resolve scenario dialog. I am disabling // this because it is causing problems for BA. /*if (isDeadCrew(unit.getEntity())) { return new ArrayList<Person>(); }*/ // Generate solo crews if (u.usesSoloPilot()) { Person p = null; if (u.getEntity() instanceof Mech) { p = c.newPerson(Person.T_MECHWARRIOR); p.addSkill(SkillType.S_PILOT_MECH, SkillType.getType(SkillType.S_PILOT_MECH).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_MECH, SkillType.getType(SkillType.S_GUN_MECH).getTarget() - oldCrew.getGunnery(), 0); } else if (u.getEntity() instanceof Aero) { p = c.newPerson(Person.T_AERO_PILOT); p.addSkill(SkillType.S_PILOT_AERO, SkillType.getType(SkillType.S_PILOT_AERO).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_AERO, SkillType.getType(SkillType.S_GUN_AERO).getTarget() - oldCrew.getGunnery(), 0); } else if (u.getEntity() instanceof ConvFighter) { p = c.newPerson(Person.T_CONV_PILOT); p.addSkill(SkillType.S_PILOT_JET, SkillType.getType(SkillType.S_PILOT_JET).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_JET, SkillType.getType(SkillType.S_GUN_JET).getTarget() - oldCrew.getPiloting(), 0); } else if (u.getEntity() instanceof Protomech) { p = c.newPerson(Person.T_PROTO_PILOT); //p.addSkill(SkillType.S_PILOT_PROTO, SkillType.getType(SkillType.S_PILOT_PROTO).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_PROTO, SkillType.getType(SkillType.S_GUN_PROTO).getTarget() - oldCrew.getGunnery(), 0); } else if (u.getEntity() instanceof VTOL) { p = c.newPerson(Person.T_VTOL_PILOT); p.addSkill(SkillType.S_PILOT_VTOL, SkillType.getType(SkillType.S_PILOT_VTOL).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_VEE, SkillType.getType(SkillType.S_GUN_VEE).getTarget() - oldCrew.getGunnery(), 0); } else { //assume tanker if we got here p = c.newPerson(Person.T_GVEE_DRIVER); p.addSkill(SkillType.S_PILOT_GVEE, SkillType.getType(SkillType.S_PILOT_GVEE).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_VEE, SkillType.getType(SkillType.S_GUN_VEE).getTarget() - oldCrew.getGunnery(), 0); } drivers.add(p); } else { // Generate drivers for multi-crewed vehicles. //Uggh, BA are a nightmare. The getTotalDriverNeeds will adjust for missing/destroyed suits //but we can't change that because lots of other stuff needs that to be right, so we will hack //it here to make it the starting squad size int driversNeeded = u.getTotalDriverNeeds(); if (u.getEntity() instanceof BattleArmor) { driversNeeded = ((BattleArmor) u.getEntity()).getSquadSize(); } while (drivers.size() < driversNeeded) { Person p = null; if (u.getEntity() instanceof SmallCraft || u.getEntity() instanceof Jumpship) { p = c.newPerson(Person.T_SPACE_PILOT); p.addSkill(SkillType.S_PILOT_SPACE, randomSkillFromTarget( SkillType.getType(SkillType.S_PILOT_SPACE).getTarget() - oldCrew.getPiloting()), 0); totalPiloting += p.getSkill(SkillType.S_PILOT_SPACE).getFinalSkillValue(); } else if (u.getEntity() instanceof BattleArmor) { p = c.newPerson(Person.T_BA); p.addSkill(SkillType.S_GUN_BA, randomSkillFromTarget( SkillType.getType(SkillType.S_GUN_BA).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_GUN_BA).getFinalSkillValue(); } else if (u.getEntity() instanceof Infantry) { p = c.newPerson(Person.T_INFANTRY); p.addSkill(SkillType.S_SMALL_ARMS, randomSkillFromTarget( SkillType.getType(SkillType.S_SMALL_ARMS).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_SMALL_ARMS).getFinalSkillValue(); } else if (u.getEntity() instanceof VTOL) { p = c.newPerson(Person.T_VTOL_PILOT); p.addSkill(SkillType.S_PILOT_VTOL, SkillType.getType(SkillType.S_PILOT_VTOL).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_VEE, SkillType.getType(SkillType.S_GUN_VEE).getTarget() - oldCrew.getGunnery(), 0); } else { //assume tanker if we got here p = c.newPerson(Person.T_GVEE_DRIVER); p.addSkill(SkillType.S_PILOT_GVEE, SkillType.getType(SkillType.S_PILOT_GVEE).getTarget() - oldCrew.getPiloting(), 0); p.addSkill(SkillType.S_GUN_VEE, SkillType.getType(SkillType.S_GUN_VEE).getTarget() - oldCrew.getGunnery(), 0); } drivers.add(p); } // Regenerate as needed to balance if (drivers.size() != 0) { averageGunnery = (int) Math.round(((double) totalGunnery) / drivers.size()); averagePiloting = (int) Math.round(((double) totalPiloting) / drivers.size()); if (u.getEntity() instanceof SmallCraft || u.getEntity() instanceof Jumpship) { while (averagePiloting != oldCrew.getPiloting()) { totalPiloting = 0; for (Person p : drivers) { p.addSkill(SkillType.S_PILOT_SPACE, randomSkillFromTarget( SkillType.getType(SkillType.S_PILOT_SPACE).getTarget() - oldCrew.getPiloting()), 0); totalPiloting += p.getSkill(SkillType.S_PILOT_SPACE).getFinalSkillValue(); } averagePiloting = (int) Math.round(((double) totalPiloting) / drivers.size()); } } else if (u.getEntity() instanceof BattleArmor) { while (averageGunnery != oldCrew.getGunnery()) { totalGunnery = 0; for (Person p : drivers) { p.addSkill(SkillType.S_GUN_BA, randomSkillFromTarget( SkillType.getType(SkillType.S_GUN_BA).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_GUN_BA).getFinalSkillValue(); } averageGunnery = (int) Math.round(((double) totalGunnery) / drivers.size()); } } else if (u.getEntity() instanceof Infantry) { while (averageGunnery != oldCrew.getGunnery()) { totalGunnery = 0; for (Person p : drivers) { p.addSkill(SkillType.S_SMALL_ARMS, randomSkillFromTarget( SkillType.getType(SkillType.S_SMALL_ARMS).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_SMALL_ARMS).getFinalSkillValue(); } averageGunnery = (int) Math.round(((double) totalGunnery) / drivers.size()); } } } if (!u.usesSoldiers()) { // Generate gunners for multi-crew vehicles while (gunners.size() < u.getTotalGunnerNeeds()) { Person p = null; if (u.getEntity() instanceof SmallCraft || u.getEntity() instanceof Jumpship) { p = c.newPerson(Person.T_SPACE_GUNNER); p.addSkill(SkillType.S_GUN_SPACE, randomSkillFromTarget( SkillType.getType(SkillType.S_GUN_SPACE).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_GUN_SPACE).getFinalSkillValue(); } else { //assume tanker if we got here p = c.newPerson(Person.T_VEE_GUNNER); p.addSkill(SkillType.S_GUN_VEE, randomSkillFromTarget( SkillType.getType(SkillType.S_GUN_VEE).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_GUN_VEE).getFinalSkillValue(); } gunners.add(p); } // Regenerate gunners as needed to balance if (gunners.size() != 0) { averageGunnery = (int) Math.round(((double) totalGunnery) / gunners.size()); if (u.getEntity() instanceof Tank) { while (averageGunnery != oldCrew.getGunnery()) { totalGunnery = 0; for (Person p : gunners) { p.addSkill(SkillType.S_GUN_VEE, randomSkillFromTarget( SkillType.getType(SkillType.S_GUN_VEE).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_GUN_VEE).getFinalSkillValue(); } averageGunnery = (int) Math.round(((double) totalGunnery) / gunners.size()); } } else if (u.getEntity() instanceof SmallCraft || u.getEntity() instanceof Jumpship) { while (averageGunnery != oldCrew.getGunnery()) { totalGunnery = 0; for (Person p : gunners) { p.addSkill(SkillType.S_GUN_SPACE, randomSkillFromTarget(SkillType.getType(SkillType.S_GUN_SPACE).getTarget() - oldCrew.getGunnery()), 0); totalGunnery += p.getSkill(SkillType.S_GUN_SPACE).getFinalSkillValue(); } averageGunnery = (int) Math.round(((double) totalGunnery) / gunners.size()); } } } } } boolean nameset = false; while (vesselCrew.size() < u.getTotalCrewNeeds()) { Person p = c.newPerson(Person.T_SPACE_CREW); if (!nameset) { p.setName(commanderName); nameset = true; } vesselCrew.add(p); } if (u.canTakeNavigator()) { Person p = c.newPerson(Person.T_NAVIGATOR); navigator = p; } for (Person p : drivers) { if (!nameset) { p.setName(commanderName); nameset = true; } } for (Person p : gunners) { if (!nameset) { p.setName(commanderName); nameset = true; } } for (Person p : vesselCrew) { if (!nameset) { p.setName(commanderName); nameset = true; } } if (null != navigator) { if (!nameset) { navigator.setName(commanderName); nameset = true; } } // Gather the data Map<CrewType, Collection<Person>> result = new HashMap<>(); if (!drivers.isEmpty()) { if (u.usesSoloPilot()) { result.put(CrewType.PILOT, drivers); } else if (u.usesSoldiers()) { result.put(CrewType.SOLDIER, drivers); } else { result.put(CrewType.DRIVER, drivers); } } if (!gunners.isEmpty()) { result.put(CrewType.GUNNER, gunners); } if (!vesselCrew.isEmpty()) { result.put(CrewType.VESSEL_CREW, vesselCrew); } if (null != navigator) { result.put(CrewType.NAVIGATOR, Collections.singletonList(navigator)); } return result; } public static int generateRandomExp() { int roll = Compute.randomInt(100); if (roll < 20) { // 20% chance of a randomized xp return (Compute.randomInt(8) + 1); } else if (roll < 40) { // 20% chance of 3 xp return 3; } else if (roll < 60) { // 20% chance of 2 xp return 2; } else if (roll < 80) { // 20% chance of 1 xp return 1; } return 0; // 20% chance of no xp } public static int rollSpecialAbilities(int bonus) { int roll = Compute.d6(2) + bonus; if (roll < 10) { return 0; } else if (roll < 12) { return 1; } else { return 2; } } public static boolean rollProbability(int prob) { return Compute.randomInt(100) <= prob; } public static int getAgeByExpLevel(int expLevel, boolean clan) { int baseage = 19; int ndice = 1; switch (expLevel) { case (SkillType.EXP_REGULAR): ndice = 2; break; case (SkillType.EXP_VETERAN): ndice = 3; break; case (SkillType.EXP_ELITE): ndice = 4; break; } int age = baseage; while (ndice > 0) { int roll = Compute.d6(); //reroll all sixes once if (roll == 6) { roll += (Compute.d6() - 1); } if (clan) { roll = (int) Math.ceil(roll / 2.0); } age += roll; ndice--; } return age; } public static String getOptionDisplayName(IOption option) { String name = option.getDisplayableNameWithValue(); name = name.replaceAll("\\(.+?\\)", ""); //$NON-NLS-1$ //$NON-NLS-2$ if (option.getType() == IOption.CHOICE) { name += " - " + option.getValue(); //$NON-NLS-1$ } return name; } public static String printIntegerArray(int[] array) { String values = ""; //$NON-NLS-1$ for (int i = 0; i < array.length; i++) { values += Integer.toString(array[i]); if (i < (array.length - 1)) { values += ","; //$NON-NLS-1$ } } return values; } public static String printDoubleArray(double[] array) { String values = ""; //$NON-NLS-1$ for (int i = 0; i < array.length; i++) { values += Double.toString(array[i]); if (i < (array.length - 1)) { values += ","; //$NON-NLS-1$ } } return values; } public static String printBooleanArray(boolean[] array) { String values = ""; //$NON-NLS-1$ for (int i = 0; i < array.length; i++) { values += Boolean.toString(array[i]); if (i < (array.length - 1)) { values += ","; //$NON-NLS-1$ } } return values; } public static int getSimpleTechLevel(int level) { switch (level) { case TechConstants.T_ALLOWED_ALL: case TechConstants.T_INTRO_BOXSET: return CampaignOptions.TECH_INTRO; case TechConstants.T_IS_TW_NON_BOX: case TechConstants.T_CLAN_TW: case TechConstants.T_IS_TW_ALL: case TechConstants.T_TW_ALL: return CampaignOptions.TECH_STANDARD; case TechConstants.T_IS_ADVANCED: case TechConstants.T_CLAN_ADVANCED: return CampaignOptions.TECH_ADVANCED; case TechConstants.T_IS_EXPERIMENTAL: case TechConstants.T_CLAN_EXPERIMENTAL: return CampaignOptions.TECH_EXPERIMENTAL; case TechConstants.T_IS_UNOFFICIAL: case TechConstants.T_CLAN_UNOFFICIAL: return CampaignOptions.TECH_UNOFFICIAL; case TechConstants.T_TECH_UNKNOWN: return CampaignOptions.TECH_UNKNOWN; default: return CampaignOptions.TECH_INTRO; } } //copied from http://www.roseindia.net/java/beginners/copyfile.shtml public static void copyfile(File inFile, File outFile) { try { InputStream in = new FileInputStream(inFile); //For Append the file. // OutputStream out = new FileOutputStream(f2,true); //For Overwrite the file. OutputStream out = new FileOutputStream(outFile); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.close(); System.out.println("File copied."); //$NON-NLS-1$ } catch (FileNotFoundException ex) { System.out.println(ex.getMessage() + " in the specified directory."); //$NON-NLS-1$ } catch (IOException e) { System.out.println(e.getMessage()); } } public static void unscrambleEquipmentNumbers(Unit unit) { ArrayList<Integer> equipNums = new ArrayList<Integer>(); for (Mounted m : unit.getEntity().getEquipment()) { equipNums.add(unit.getEntity().getEquipmentNum(m)); } for (Part part : unit.getParts()) { if (!(part instanceof EquipmentPart)) { continue; } EquipmentPart ep = ((EquipmentPart) part); Mounted m = unit.getEntity().getEquipment(ep.getEquipmentNum()); //Taharqa: I am not sure what was supposed to go in here, but it doesn't actually //do anything at this point and it is producing an NPE on some refits, so I am //commenting it out //if (m.getType().getInternalName().equals(ep.getType().getInternalName())) { //} if (part instanceof AmmoBin) { AmmoBin bin = (AmmoBin) part; int i = -1; boolean found = false; for (int equipNum : equipNums) { i++; m = unit.getEntity().getEquipment(equipNum); if (!(m.getType() instanceof AmmoType)) { continue; } if (m.getType().getInternalName().equals(bin.getType().getInternalName()) && ((AmmoType) m.getType()).getMunitionType() == bin.getMunitionType() && !m.isDestroyed()) { bin.setEquipmentNum(equipNum); found = true; break; } } if (found) { equipNums.remove(i); } } else if (part instanceof MissingAmmoBin) { MissingAmmoBin bin = (MissingAmmoBin) part; int i = -1; boolean found = false; for (int equipNum : equipNums) { i++; m = unit.getEntity().getEquipment(equipNum); if (!(m.getType() instanceof AmmoType)) { continue; } if (m.getType().getInternalName().equals(bin.getType().getInternalName()) && m.isDestroyed()) { bin.setEquipmentNum(equipNum); found = true; break; } } if (found) { equipNums.remove(i); } } else if (part instanceof EquipmentPart) { EquipmentPart epart = (EquipmentPart) part; int i = -1; boolean found = false; for (int equipNum : equipNums) { i++; m = unit.getEntity().getEquipment(equipNum); if (m.getType() instanceof AmmoType) { continue; } if (m.getType().getInternalName().equals(epart.getType().getInternalName()) && !m.isDestroyed()) { epart.setEquipmentNum(equipNum); found = true; break; } } if (found) { equipNums.remove(i); } } else if (part instanceof MissingEquipmentPart) { MissingEquipmentPart epart = (MissingEquipmentPart) part; int i = -1; boolean found = false; for (int equipNum : equipNums) { i++; m = unit.getEntity().getEquipment(equipNum); if (m.getType().getInternalName().equals(epart.getType().getInternalName()) && m.isDestroyed()) { epart.setEquipmentNum(equipNum); found = true; break; } } if (found) { equipNums.remove(i); } } } } public static int getDaysBetween(Date date1, Date date2) { return (int) ((date2.getTime() - date1.getTime()) / MILLISECONDS_IN_DAY); } /** * Calculates the number of days between start and end dates, taking * into consideration leap years, year boundaries etc. * * @param start the start date * @param end the end date, must be later than the start date * @return the number of days between the start and end dates */ public static long countDaysBetween(Date start, Date end) { if (end.before(start)) { throw new IllegalArgumentException("The end date must be later than the start date"); } //reset all hours mins and secs to zero on start date Calendar startCal = GregorianCalendar.getInstance(); startCal.setTime(start); startCal.set(Calendar.HOUR_OF_DAY, 0); startCal.set(Calendar.MINUTE, 0); startCal.set(Calendar.SECOND, 0); long startTime = startCal.getTimeInMillis(); //reset all hours mins and secs to zero on end date Calendar endCal = GregorianCalendar.getInstance(); endCal.setTime(end); endCal.set(Calendar.HOUR_OF_DAY, 0); endCal.set(Calendar.MINUTE, 0); endCal.set(Calendar.SECOND, 0); long endTime = endCal.getTimeInMillis(); return (endTime - startTime) / MILLISECONDS_IN_DAY; } public static int getDiffFullYears(Date date, GregorianCalendar b) { GregorianCalendar a = new GregorianCalendar(); a.setTime(date); int diff = b.get(GregorianCalendar.YEAR) - a.get(GregorianCalendar.YEAR); if (a.get(GregorianCalendar.MONTH) > b.get(GregorianCalendar.MONTH) || (a.get(GregorianCalendar.MONTH) == b.get(GregorianCalendar.MONTH) && a.get(GregorianCalendar.DATE) > b.get(GregorianCalendar.DATE))) { diff--; } return diff; } public static int getDiffPartialYears(Date date, GregorianCalendar b) { GregorianCalendar a = new GregorianCalendar(); a.setTime(date); int diff = b.get(GregorianCalendar.YEAR) - a.get(GregorianCalendar.YEAR); if (diff == 0 && countDaysBetween(a.getTime(), b.getTime()) > 0) { return 1; } return diff; } /** @return the current date as a DateTime time stamp for midnight in UTC time zone */ public static DateTime getDateTimeDay(Calendar cal) { return new LocalDateTime(cal).toDateTime(DateTimeZone.UTC); } /** @return the current date as a DateTime time stamp for midnight in UTC time zone */ public static DateTime getDateTimeDay(Date date) { return new LocalDateTime(date).toDateTime(DateTimeZone.UTC); } /** * export a jtable to TSV * code derived from: * https://sites.google.com/site/teachmemrxymon/java/export-records-from-jtable-to-ms-excel * @param table * @param file */ public static void exportTabletoCSV(JTable table, File file) { try { TableModel model = table.getModel(); FileWriter csv = new FileWriter(file); for (int i = 0; i < model.getColumnCount(); i++) { String s = model.getColumnName(i); if (null == s) { s = ""; //$NON-NLS-1$ } if (s.contains("\"")) { //$NON-NLS-1$ s = s.replace("\"", "\"\""); //$NON-NLS-1$ //$NON-NLS-2$ } s = "\"" + s + "\""; //$NON-NLS-1$ //$NON-NLS-2$ csv.write(s + ","); //$NON-NLS-1$ } csv.write("\n"); //$NON-NLS-1$ for (int i = 0; i < model.getRowCount(); i++) { for (int j = 0; j < model.getColumnCount(); j++) { String s = model.getValueAt(i, j).toString(); if (null == s) { s = ""; //$NON-NLS-1$ } if (s.contains("\"")) { //$NON-NLS-1$ s = s.replace("\"", "\"\""); //$NON-NLS-1$ //$NON-NLS-2$ } s = "\"" + s + "\""; //$NON-NLS-1$ //$NON-NLS-2$ csv.write(s + ","); //$NON-NLS-1$ } csv.write("\n"); //$NON-NLS-1$ } csv.close(); } catch (IOException e) { System.out.println(e); } } public static Vector<String> splitString(String str, String sep) { StringTokenizer st = new StringTokenizer(str, sep); Vector<String> output = new Vector<String>(); while (st.hasMoreTokens()) { output.add(st.nextToken()); } return output; } public static String combineString(Collection<String> vec, String sep) { if ((null == vec) || (null == sep)) { return null; } StringBuilder sb = new StringBuilder(); boolean first = true; for (String part : vec) { if (first) { first = false; } else { sb.append(sep); } sb.append(part); } return sb.toString(); } /** @return the input string with all words capitalized */ public static String capitalize(String str) { if ((null == str) || str.isEmpty()) { return str; } final char[] buffer = str.toCharArray(); boolean capitalizeNext = true; for (int i = 0; i < buffer.length; ++i) { final char ch = buffer[i]; if (Character.isWhitespace(ch)) { capitalizeNext = true; } else if (capitalizeNext) { buffer[i] = Character.toTitleCase(ch); capitalizeNext = false; } } return new String(buffer); } public static String getRomanNumeralsFromArabicNumber(int level, boolean checkZero) { // If we're 0, then we just return an empty string if (checkZero && level == 0) { return ""; //$NON-NLS-1$ } // Roman numeral, prepended with a space for display purposes String roman = " "; //$NON-NLS-1$ int num = level + 1; for (int i = 0; i < arabicNumbers.length; i++) { while (num > arabicNumbers[i]) { roman += romanNumerals[i]; num -= arabicNumbers[i]; } } return roman; } // TODO: Optionize this to allow user to choose roman or arabic numerals public static int getArabicNumberFromRomanNumerals(String name) { // If we're 0, then we just return an empty string if (name.equals("")) { //$NON-NLS-1$ return 0; } // Roman numeral, prepended with a space for display purposes int arabic = 0; String roman = name; for (int i = 0; i < roman.length(); i++) { int num = romanNumerals.toString().indexOf(roman.charAt(i)); if (i < roman.length()) { int temp = romanNumerals.toString().indexOf(roman.charAt(i + 1)); // If this is a larger number, then we need to combine them if (temp > num) { num = temp - num; i++; } } arabic += num; } return arabic - 1; } public static Map<String, Integer> sortMapByValue(Map<String, Integer> unsortMap, boolean highFirst) { // Convert Map to List List<Map.Entry<String, Integer>> list = new LinkedList<Map.Entry<String, Integer>>(unsortMap.entrySet()); // Sort list with comparator, to compare the Map values Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() { @Override public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { return (o1.getValue()).compareTo(o2.getValue()); } }); // Convert sorted map back to a Map Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>(); if (highFirst) { ListIterator<Map.Entry<String, Integer>> li = list.listIterator(list.size()); while (li.hasPrevious()) { Map.Entry<String, Integer> entry = li.previous(); sortedMap.put(entry.getKey(), entry.getValue()); } } else { for (Iterator<Map.Entry<String, Integer>> it = list.iterator(); it.hasNext();) { Map.Entry<String, Integer> entry = it.next(); sortedMap.put(entry.getKey(), entry.getValue()); } } return sortedMap; } public static boolean isLikelyCapture(Entity en) { //most of these conditions are now controlled better in en.canEscape, but there //are some additional ones we want to add if (!en.canEscape()) { return true; } return en.isDestroyed() || en.isDoomed() || en.isStalled() || en.isStuck(); } /** * Run through the directory and call parser.parse(fis) for each XML file found. Don't recurse. */ public static void parseXMLFiles(String dirName, FileParser parser) { parseXMLFiles(dirName, parser, false); } /** * Run through the directory and call parser.parse(fis) for each XML file found. */ public static void parseXMLFiles(String dirName, FileParser parser, boolean recurse) { if (null == dirName || null == parser) { throw new NullPointerException(); } File dir = new File(dirName); if (dir.isDirectory()) { File[] files = dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase(Locale.ROOT).endsWith(".xml"); //$NON-NLS-1$ } }); if (null != files && files.length > 0) { // Case-insensitive sorting. Yes, even on Windows. Deal with it. Arrays.sort(files, new Comparator<File>() { @Override public int compare(File f1, File f2) { return f1.getPath().compareTo(f2.getPath()); } }); // Try parsing and updating the main list, one by one for (File file : files) { if (file.isFile()) { try (FileInputStream fis = new FileInputStream(file)) { parser.parse(fis); } catch (Exception ex) { // Ignore this file then MekHQ.logError("Exception trying to parse " + file.getPath() + " - ignoring."); //$NON-NLS-1$ //$NON-NLS-2$ MekHQ.logError(ex); } } } } if (!recurse) { // We're done return; } // Get subdirectories too File[] dirs = dir.listFiles(); if (null != dirs && dirs.length > 0) { Arrays.sort(dirs, new Comparator<File>() { @Override public int compare(File f1, File f2) { return f1.getPath().compareTo(f2.getPath()); } }); for (File subDirectory : dirs) { if (subDirectory.isDirectory()) { parseXMLFiles(subDirectory.getPath(), parser, recurse); } } } } } }