/* * Copyright (C) 2016 MegaMek team * * 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 2 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.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeSet; import megamek.common.EquipmentType; import mekhq.MekHQ; import mekhq.Utilities; import mekhq.campaign.universe.Planet.SpectralDefinition; /** Static method only helper class for stars */ public final class StarUtil { // A bunch of important astronomical constants /** Speed of light in a vacuum in km/s (only really important for non-hyperspace comms and sensors) */ public static final double C = 299792.458; /** Julian year length in seconds (IAU standard astronomical year) */ public static final double Y_JULIAN = 365.25 * 86400; /** * Light year in km (IAU standard as the speed of light times 365.25-day Julian Year, * also ISO 80000-3 Annex C */ public static final double LY = C * Y_JULIAN; /** Astronomical unit in km (IAU standard, also ISO 80000-3 Annex C) */ public static final double AU = 149597870.7; /** Standard gravity in m/s^2 (ISO 80000-3) */ public static final double G = 9.80665; /** Solar luminosity in W */ public static final double SOLAR_LUM = 3.846e26; /** Solar mass in kg */ public static final double SOLAR_MASS = 1.98855e30; /** Solar radius in km */ public static final double SOLAR_RADIUS = 695700.0; /** Effective solar temperature in K */ public static final double SOLAR_TEMP = 5778.0; /** Gravitational constant (in m^3 kg^-1 s^-2 */ public static final double GRAV_CONSTANT = 6.673848e-11; // Temperature, mass, luminosity and size are all linked together. When modifying those tables, // make VERY sure you don't accidentally break the inherent inequalities // (stars becoming smaller, dimmer, cooler and so on in one direction) // Temperature ranges for generation only. "Real" stars can lie outside of those. private static final int[] TEMPERATURE_RANGES = { 65000, // Above class O 57500, 53500, 50000, 46500, 43500, 40500, 37500, 34500, 31500, 30000, // Class O 27000, 25000, 23000, 21000, 19500, 18000, 16500, 14000, 12000, 10000, // Class B 9700, 9450, 9300, 9000, 8800, 8500, 8250, 8000, 7750, 7500, // Class A 7350, 7200, 7050, 6900, 6800, 6650, 6500, 6350, 6200, 6000, // Class F 5900, 5800, 5700, 5600, 5500, 5400, 5350, 5300, 5250, 5200, // Class G 5100, 5000, 4900, 4800, 4650, 4500, 4300, 4100, 3900, 3700, // Class K 3600, 3400, 3250, 3100, 2950, 2800, 2700, 2600, 2500, 2400, // Class M 2290, 2180, 2070, 1960, 1850, 1740, 1630, 1520, 1410, 1300, // Class L 1200, 1100, 1000, 900, 800, 700, 650, 600, 550, 500 // Class T }; // Mass ranges in solar masses for generation purpose private static final double[] MIN_MASS = { 2437.5, 1235, 837.5, 538.2, 371, 234.3, 151.2, 94.9, 57.72, 35.25, // Class O 23.1, 15.6, 11.85, 8.08, 6.561, 5.494, 4.7891, 4.0338, 3.3012, 2.6628, // Class B 2.091, 1.938, 1.8662, 1.7802, 1.74, 1.6965, 1.672, 1.584, 1.5575, 1.513, // Class A 1.44, 1.395, 1.35, 1.305, 1.26, 1.215, 1.17, 1.125, 1.08, 1.035, // Class F 0.99, 0.945, 0.9, 0.8775, 0.855, 0.8325, 0.81, 0.7875, 0.765, 0.7875, // Class G 0.712, 0.68975, 0.66, 0.638, 0.609, 0.5655, 0.516, 0.4945, 0.4675, 0.44625, // Class K 0.42, 0.378, 0.332, 0.2905, 0.2255, 0.164, 0.1215, 0.081, 0.072, 0.06, // Class M 0.044, 0.036, 0.0296, 0.0248, 0.0216, 0.0176, 0.0152, 0.01275, 0.0126, 0.01183, // Class L 0.01104, 0.010695, 0.01034, 0.01026, 0.010176, 0.010088, 0.009996, 0.0099, 0.009702, 0.009504 // Class T }; private static final double[] MAX_MASS = { 5062.5, 2565, 1662.5, 1021.8, 689, 425.7, 268.8, 165.1, 98.28, 58.75, // Class O 36.9, 24.4, 18.15, 12.12, 9.639, 7.906, 6.7509, 5.6862, 4.5588, 3.6772, // Class B 2.829, 2.622, 2.4738, 2.3598, 2.26, 2.2035, 2.128, 2.016, 1.9425, 1.887, // Class A 1.76, 1.705, 1.65, 1.595, 1.54, 1.485, 1.43, 1.375, 1.32, 1.265, // Class F 1.21, 1.155, 1.1, 1.0725, 1.045, 1.0175, 0.99, 0.9625, 0.935, 0.9625, // Class G 0.888, 0.86025, 0.84, 0.812, 0.791, 0.7345, 0.684, 0.6555, 0.6325, 0.60375, // Class K 0.58, 0.522, 0.468, 0.4095, 0.3245, 0.236, 0.1785, 0.119, 0.108, 0.09, // Class M 0.066, 0.054, 0.0444, 0.0372, 0.0324, 0.0264, 0.0228, 0.01725, 0.0154, 0.01417, // Class L 0.01296, 0.012305, 0.01166, 0.01134, 0.011024, 0.010712, 0.010404, 0.0101, 0.009898, 0.009696 // Class T }; // Average luminosity in terms of Solar luminosity. O-class stars are BRIGHT. Generated values are +/-10% of this. private static final double[] AVG_LUMINOSITY = { 12000000, 6000000, 4000000, 2500000, 1700000, 1050000, 670000, 410000, 250000, 150000, // Class O 96000, 64000, 19600, 4890, 2290, 1160, 692, 380, 180, 85, // Class B 35, 27, 22.5, 19, 16, 13.8, 12, 10.6, 9.7, 8.85, // Class A 7.5, 6.56, 5.8, 5.2, 4.4, 3.75, 3.13, 2.62, 2.41, 2.03, // Class F 1.72, 1.46, 1.23, 1.15, 0.98, 0.84, 0.76, 0.68, 0.65, 0.59, // Class G 0.543, 0.475, 0.41, 0.355, 0.31, 0.257, 0.211, 0.187, 0.155, 0.125, // Class K 0.1, 0.0535, 0.0321, 0.0178, 0.0106, 0.0063, 0.0045, 0.0016, 0.0008, 0.0006, // Class M 0.00025, 0.00017, 0.00012, 0.00008, 0.000055, 0.000035, 0.000025, 0.000015, 0.000009, 0.000006, // Class L 0.0000035, 0.000002, 0.0000012, 0.0000007, 0.00000035, 0.00000018, 0.00000011, 0.00000007, 0.00000004, 0.000000025 // Class T }; //taken from Dropships and Jumpships sourcebook, pg. 17. L- and T-classes estimated private static final double[] DISTANCE_TO_JUMP_POINT = { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 347840509855.0, 282065439915.0, 229404075188.0, 187117766777.0, 153063985045.0, // Class B 125563499718.0, 103287722257.0, 85198295036.0, 70467069133.0, 58438309136.0, 48590182199.0, 40506291619.0, 33853487850.0, 28364525294.0, 23824470101.0, // Class A 20060019532.0, 16931086050.0, 14324152109.0, 12147004515.0, 10324556364.0, 8795520975.0, 7509758447.0, 6426154651.0, 5510915132.0, 4736208289.0, // Class F 4079054583.0, 3520442982.0, 3044611112.0, 2638462416.0, 2291092549.0, 1993403717.0, 1737789950.0, 1517879732.0, 1328325100.0, 1164628460.0, // Class G 1023000099.0, 900240718.0, 793644393.0, 700918272.0, 620115976.0, 549582283.0, 487907078.0, 433886958.0, 386493164.0, 344844735.0, // Class K 308186014.0, 275867748.0, 247331200.0, 222094749.0, 199742590.0, 179915179.0, 162301133.0, 146630374.0, 132668292.0, 120210786.0, // Class M 109080037.0, 99120895.0, 90197803.0, 82192147.0, 75000000.0, 64303323.0, 58164544.0, 52741556.0, 48276182.0, 45054062.0, // Class L 40668992.0, 37794523.0, 33581315.0, 32442633.0, 31262503.0, 30036042.0, 29403633.0, 28757320.0, 28494691.0, 28229618.0, // Class T 27962033.0, 27691862.0, 27419029.0, 27143454.0, 26865052.0 }; // Slightly modified IO Beta table private static final int[] REALISTIC_SPECTRAL_TYPE = { Planet.SPECTRAL_F, Planet.SPECTRAL_M, Planet.SPECTRAL_G, Planet.SPECTRAL_K, Planet.SPECTRAL_M, Planet.SPECTRAL_M, Planet.SPECTRAL_M, Planet.SPECTRAL_M, Planet.SPECTRAL_M, Planet.SPECTRAL_L, -1}; private static final int[] HOT_SPECTRAL_TYPE = { Planet.SPECTRAL_B, Planet.SPECTRAL_B, Planet.SPECTRAL_A, Planet.SPECTRAL_A, Planet.SPECTRAL_A, Planet.SPECTRAL_F, Planet.SPECTRAL_F, Planet.SPECTRAL_F, Planet.SPECTRAL_F, Planet.SPECTRAL_F, Planet.SPECTRAL_F}; private static final int[] LIFEFRIENDLY_SPECTRAL_TYPE = new int[]{ Planet.SPECTRAL_M, Planet.SPECTRAL_M, Planet.SPECTRAL_M, Planet.SPECTRAL_K, Planet.SPECTRAL_K, Planet.SPECTRAL_G, Planet.SPECTRAL_G, Planet.SPECTRAL_F, Planet.SPECTRAL_F, Planet.SPECTRAL_F, Planet.SPECTRAL_F}; private static final double[] MIN_LIFE_ZONE = { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 18836034615.0, 13789104394.0, 9577962205.0, 6922924960.0, 4737540501.0, // Class B 3371818500.0, 2604283395.0, 1989875373.0, 1438058066.0, 1079962499.0, 812765280.0, 694412846.0, 621417251.0, 532211330.0, 476847145.0, // Class A 408187457.0, 384701313.0, 345792134.0, 326849966.0, 294514601.0, 278962256.0, 253563720.0, 241486956.0, 220038497.0, 210010714.0, // Class F 191712676.0, 175148880.0, 160245499.0, 153689329.0, 141053288.0, 129837283.0, 119622155.0, 98151248.0, 91688535.0, 86213444.0, // Class G 82535447.0, 77425112.0, 74433863.0, 70141642.0, 66581180.0, 63003696.0, 51915431.0, 43693947.0, 37332074.0, 32571422.0, // Class K 28624229.0, 26182800.0, 24000141.0, 22440922.0, 21060769.0, 19622213.0, 16407340.0, 13437355.0, 10606623.0, 8957198.0, // Class M 7346411.0, 5735514.0, 4373667.0, 3208345.0, 2319138.0, 2055095.0, 1833752.0, 1627530.0, 1435990.0, 1258696.0, // Class L 1095210.0, 945094.0, 807910.0, 683220.0, 570588.0, 477499.0, 393937.0, 319539.0, 253943.0, 196788.0, // Class T 147711.0, 124816.0, 104182.0, 85718.0, 69334.0 }; private static final double[] MAX_LIFE_ZONE = { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 38242858157.0, 27996060437.0, 19446165689.0, 14055635525.0, 9618642836.0, // Class B 6845813319.0, 5287484468.0, 4040050000.0, 2919693648.0, 2192651135.0, 1650159810.0, 1409868505.0, 1261665328.0, 1080550276.0, 968144204.0, // Class A 828744231.0, 781060241.0, 702062818.0, 663604476.0, 597953886.0, 566377913.0, 514811189.0, 490291699.0, 446744826.0, 426385389.0, // Class F 389234826.0, 355605301.0, 325346923.0, 312035911.0, 286380918.0, 263609029.0, 242869224.0, 199629657.0, 186594212.0, 175399766.0, // Class G 167822075.0, 158854972.0, 150108291.0, 142701962.0, 135419349.0, 128218049.0, 105827610.0, 89287631.0, 76400524.0, 65978008.0, // Class K 58795714.0, 53743641.0, 49297586.0, 46062946.0, 42690748.0, 39244426.0, 33187574.0, 27680951.0, 21613496.0, 18377700.0, // Class M 15048294.0, 11772898.0, 8929569.0, 6594932.0, 4638276.0, 4572412.0, 4079942.0, 3621114.0, 3194956.0, 2800492.0, // Class L 2436749.0, 2102753.0, 1797530.0, 1520107.0, 1269509.0, 1062395.0, 876476.0, 710946.0, 565001.0, 437836.0, // Class T 328645.0, 277705.0, 231795.0, 190715.0, 154262.0 }; private static final double[] RECHARGE_HOURS_CLASS_L = { 512.0, 616.0, 717.0, 901.0, 1142.0, 1462.0, 1767.0, 2325.0, 3617.0, 5038.0, 7973.0}; private static final double[] RECHARGE_HOURS_CLASS_T = { 7973.0, 13371.0, 21315.0, 35876.0, 70424.0, 134352.0, 215620.0, 32188.0, 569703.0, 892922.0, 10000000.0}; private static final Set<String> VALID_WHITE_DWARF_SUBCLASSES = new TreeSet<String>(); static { VALID_WHITE_DWARF_SUBCLASSES.addAll(Arrays.asList("", //$NON-NLS-1$ "A,B,O,Q,Z,AB,AO,AQ,AZ,BO,BQ,BZ,QZ,ABO,ABQ,ABZ,AOQ,AOZ,AQZ,BOQ," //$NON-NLS-1$ + "BOZ,BQZ,OQZ,ABOQ,ABOZ,ABQZ,AOQZ,BOQZ,ABOQZ,C,X".split(","))); //$NON-NLS-1$ //$NON-NLS-2$ } private static final String ICON_DATA_FILE = "images/universe/planet_icons.txt"; private static final Map<String, String> ICON_DATA = new HashMap<>(); private static boolean iconDataLoaded = false; // Generators public static String generateSpectralType(Random rnd, boolean lifeFriendly) { return generateSpectralType(rnd, lifeFriendly, -1); } public static String generateSpectralType(Random rnd, boolean lifeFriendly, int spectralTypeOverride) { int spectralType = spectralTypeOverride; if(spectralTypeOverride < 0) { if( lifeFriendly ) { spectralType = LIFEFRIENDLY_SPECTRAL_TYPE[rnd.nextInt(6) + rnd.nextInt(6)]; } else { spectralType = REALISTIC_SPECTRAL_TYPE[rnd.nextInt(6) + rnd.nextInt(6)]; if( spectralType < 0 ) { spectralType = HOT_SPECTRAL_TYPE[rnd.nextInt(6) + rnd.nextInt(6)]; } } } // Slightly weighted towards the higher numbers int subType = (int)Math.floor(Utilities.lerp(0.0, 10.0, Math.pow(rnd.nextDouble(), 0.8))); return getSpectralType(spectralType, subType * 1.0, Planet.LUM_V); } public static double generateTemperature(Random rnd, int spectral, double subtype) { return Utilities.lerp(getMinTemperature(spectral, subtype), getMaxTemperature(spectral, subtype), rnd.nextDouble()); } public static double generateMass(Random rnd, int spectral, double subtype) { return Utilities.lerp(getMinMass(spectral, subtype), getMaxMass(spectral, subtype), rnd.nextDouble()); } public static double generateLuminosity(Random rnd, int spectral, double subtype) { return getAvgLuminosity(spectral, subtype) * (rnd.nextDouble() * 0.2 + 0.9); } // Temperature data private static double getTemperatureRange(int spectralTypeNumber) { if((spectralTypeNumber >= 0) && (spectralTypeNumber < TEMPERATURE_RANGES.length)) { return TEMPERATURE_RANGES[spectralTypeNumber]; } return 0.0; } public static double getMinTemperature(int spectralTypeNumber) { return getTemperatureRange(spectralTypeNumber + 1); } public static double getMinTemperature(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getMinTemperature(spectralTypeNumber), getMinTemperature(spectralTypeNumber), remainder); } public static double getMaxTemperature(int spectralTypeNumber) { return getTemperatureRange(spectralTypeNumber); } public static double getMaxTemperature(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getMaxTemperature(spectralTypeNumber), getMaxTemperature(spectralTypeNumber), remainder); } // Mass data public static double getMinMass(int spectralTypeNumber) { if((spectralTypeNumber >= 0) && (spectralTypeNumber < MIN_MASS.length)) { return MIN_MASS[spectralTypeNumber]; } return 0.0; } public static double getMinMass(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getMinMass(spectralTypeNumber), getMinMass(spectralTypeNumber), remainder); } public static double getMaxMass(int spectralTypeNumber) { if((spectralTypeNumber >= 0) && (spectralTypeNumber < MAX_MASS.length)) { return MAX_MASS[spectralTypeNumber]; } return 0.0; } public static double getMaxMass(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getMaxMass(spectralTypeNumber), getMaxMass(spectralTypeNumber), remainder); } // Luminosity data public static double getAvgLuminosity(int spectralTypeNumber) { if((spectralTypeNumber >= 0) && (spectralTypeNumber < AVG_LUMINOSITY.length)) { return AVG_LUMINOSITY[spectralTypeNumber]; } return 0.0; } public static double getAvgLuminosity(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getAvgLuminosity(spectralTypeNumber), getAvgLuminosity(spectralTypeNumber), remainder); } public static double getDistanceToJumpPoint(int spectralTypeNumber) { if((spectralTypeNumber >= 0) && (spectralTypeNumber < DISTANCE_TO_JUMP_POINT.length)) { return DISTANCE_TO_JUMP_POINT[spectralTypeNumber]; } return 0.0; } /** * Distance to jump point given a spectral class and subtype measured in kilometers */ public static double getDistanceToJumpPoint(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getDistanceToJumpPoint(spectralTypeNumber), getDistanceToJumpPoint(spectralTypeNumber), remainder); } public static double getMaxLifeZone(int spectralTypeNumber) { if((spectralTypeNumber >= 0) && (spectralTypeNumber < MAX_LIFE_ZONE.length)) { return MAX_LIFE_ZONE[spectralTypeNumber]; } return 0.0; } public static double getMaxLifeZone(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getMaxLifeZone(spectralTypeNumber), getMaxLifeZone(spectralTypeNumber), remainder); } public static double getMinLifeZone(int spectralTypeNumber) { if((spectralTypeNumber >= 0) && (spectralTypeNumber < MIN_LIFE_ZONE.length)) { return MIN_LIFE_ZONE[spectralTypeNumber]; } return 0.0; } public static double getMinLifeZone(int spectral, double subtype) { int spectralTypeNumber = spectral * 10 + (int)subtype; double remainder = subtype - (int)subtype; return Utilities.lerp(getMinLifeZone(spectralTypeNumber), getMinLifeZone(spectralTypeNumber), remainder); } public static double getSolarRechargeTime(int spectralClass, double subtype) { if(spectralClass == Planet.SPECTRAL_Q) { // Not a star, can't recharge here return Double.POSITIVE_INFINITY; } int intSubtype = (int)subtype; if(spectralClass == Planet.SPECTRAL_T) { // months! return Utilities.lerp(RECHARGE_HOURS_CLASS_T[intSubtype], RECHARGE_HOURS_CLASS_T[intSubtype + 1], subtype - intSubtype); } else if(spectralClass == Planet.SPECTRAL_L) { // weeks! return Utilities.lerp(RECHARGE_HOURS_CLASS_L[intSubtype], RECHARGE_HOURS_CLASS_L[intSubtype + 1], subtype - intSubtype); } else { return 141 + 10*spectralClass + subtype; } } public static int getSpectralClassFrom(String spectral) { switch(spectral.trim().toUpperCase(Locale.ROOT)) { case "O": return Planet.SPECTRAL_O; //$NON-NLS-1$ case "B": return Planet.SPECTRAL_B; //$NON-NLS-1$ case "A": return Planet.SPECTRAL_A; //$NON-NLS-1$ case "F": return Planet.SPECTRAL_F; //$NON-NLS-1$ case "G": return Planet.SPECTRAL_G; //$NON-NLS-1$ case "K": return Planet.SPECTRAL_K; //$NON-NLS-1$ case "M": return Planet.SPECTRAL_M; //$NON-NLS-1$ case "L": return Planet.SPECTRAL_L; //$NON-NLS-1$ case "T": return Planet.SPECTRAL_T; //$NON-NLS-1$ case "Y": return Planet.SPECTRAL_Y; //$NON-NLS-1$ default: return -1; } } public static String getSpectralClassName(int spectral) { switch(spectral) { case Planet.SPECTRAL_O: return "O"; //$NON-NLS-1$ case Planet.SPECTRAL_B: return "B"; //$NON-NLS-1$ case Planet.SPECTRAL_A: return "A"; //$NON-NLS-1$ case Planet.SPECTRAL_F: return "F"; //$NON-NLS-1$ case Planet.SPECTRAL_G: return "G"; //$NON-NLS-1$ case Planet.SPECTRAL_K: return "K"; //$NON-NLS-1$ case Planet.SPECTRAL_M: return "M"; //$NON-NLS-1$ case Planet.SPECTRAL_L: return "L"; //$NON-NLS-1$ case Planet.SPECTRAL_T: return "T"; //$NON-NLS-1$ case Planet.SPECTRAL_Y: return "Y"; //$NON-NLS-1$ default: return "?"; //$NON-NLS-1$ } } /** @return canonical name for the given combination of spectral class, subtype and luminosity */ public static String getSpectralType(Integer spectralClass, Double subtype, String luminosity) { if( null == spectralClass || null == subtype ) { return null; } if(spectralClass == Planet.SPECTRAL_Q) { return (null != luminosity) ? "Q" + luminosity : "Q"; //$NON-NLS-1$ //$NON-NLS-2$ } // Formatting subtype value up to two decimal points, if needed int subtypeValue = (int)Math.round(subtype * 100); if( subtypeValue < 0 ) { subtypeValue = 0; } if( subtypeValue > 999 ) { subtypeValue = 999; } String subtypeFormat = "%.2f"; //$NON-NLS-1$ if( subtypeValue % 100 == 0 ) { subtypeFormat = "%.0f"; } //$NON-NLS-1$ else if( subtypeValue % 10 == 0 ) { subtypeFormat = "%.1f"; } //$NON-NLS-1$ if( luminosity == Planet.LUM_VI ) { // subdwarfs return "sd" + getSpectralClassName(spectralClass) + String.format(subtypeFormat, subtypeValue / 100.0); //$NON-NLS-1$ } else if( luminosity == Planet.LUM_VI_PLUS ) { // extreme subdwarfs return "esd" + getSpectralClassName(spectralClass) + String.format(subtypeFormat, subtypeValue / 100.0); //$NON-NLS-1$ } else if( luminosity == Planet.LUM_VII ) { // white dwarfs return String.format(Locale.ROOT, "D" + subtypeFormat, subtypeValue / 100.0); //$NON-NLS-1$ } else { // main class return String.format(Locale.ROOT, "%s" + subtypeFormat + "%s", //$NON-NLS-1$ //$NON-NLS-2$ getSpectralClassName(spectralClass), subtypeValue / 100.0, (null != luminosity ? luminosity : Planet.LUM_V)); } } /** Parser for spectral type strings */ public static SpectralDefinition parseSpectralType(String type) { if((null == type) || type.isEmpty()) { return null; } // We make sure to not rewrite the subtype, in case we need whatever special part is behind it String parsedSpectralType = type; Integer parsedSpectralClass = null; Double parsedSubtype = null; String parsedLuminosity = null; // Non-stellar objects if(type.startsWith("Q")) { return new SpectralDefinition(type, Planet.SPECTRAL_Q, 0.0, type.substring(1)); } // Subdwarf prefix parsing if( type.length() > 2 && type.startsWith("sd") ) { //$NON-NLS-1$ // subdwarf parsedLuminosity = Planet.LUM_VI; type = type.substring(2); } else if( type.length() > 3 && type.startsWith("esd") ) { //$NON-NLS-1$ // extreme subdwarf parsedLuminosity = Planet.LUM_VI_PLUS; type = type.substring(3); } if( type.length() < 1 ) { // We can't parse an empty string return null; } String mainClass = type.substring(0, 1); if( mainClass.equals("D") && type.length() > 1 && null == parsedLuminosity /* prevent "sdD..." */ ) { //$NON-NLS-1$ // white dwarf parsedLuminosity = Planet.LUM_VII; String whiteDwarfVariant = type.substring(1).replaceAll("([A-Z]*).*?$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$ if( !VALID_WHITE_DWARF_SUBCLASSES.contains(whiteDwarfVariant) ) { // Don't just make up D-class variants, that's silly ... return null; } String subTypeString = type.substring(1 + whiteDwarfVariant.length()).replaceAll("^([0-9\\.]*).*?$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$ try { parsedSubtype = Double.parseDouble(subTypeString); } catch( NumberFormatException nfex ) { return null; } // We're done here, white dwarfs have a special spectral class parsedSpectralClass = Planet.SPECTRAL_D; } else if( getSpectralClassFrom(mainClass) >= 0 ) { parsedSpectralClass = getSpectralClassFrom(mainClass); String subTypeString = type.length() > 1 ? type.substring(1).replaceAll("^([0-9\\.]*).*?$", "$1") : "5" /* default */; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ try { parsedSubtype = Double.parseDouble(subTypeString); } catch( NumberFormatException nfex ) { return null; } if( type.length() > 1 + subTypeString.length() && null == parsedLuminosity ) { // We might have a luminosity, try to parse it parsedLuminosity = validateLuminosity(type.substring(1 + subTypeString.length())); if( null != parsedLuminosity && parsedLuminosity.equals(Planet.LUM_VII) ) { // That's not how white dwarfs work return null; } } } if( null != parsedSpectralClass && null != parsedSubtype && null != parsedLuminosity ) { return new SpectralDefinition(parsedSpectralType, parsedSpectralClass, parsedSubtype, parsedLuminosity); } else { return null; } } /** * @param lc string which starts with some luminosity description * @return the canonical luminosity string based on how this string starts, or <i>null</i> if it doesn't look like luminosity */ protected static String validateLuminosity(String lc) { // The order of entries here is important if( lc.startsWith("I/II") ) { return Planet.LUM_II_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("I-II") ) { return Planet.LUM_II_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("Ib/II") ) { return Planet.LUM_II_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("Ib-II") ) { return Planet.LUM_II_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("II/III") ) { return Planet.LUM_III_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("II-III") ) { return Planet.LUM_III_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("III/IV") ) { return Planet.LUM_IV_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("III-IV") ) { return Planet.LUM_IV_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("IV/V") ) { return Planet.LUM_V_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("IV-V") ) { return Planet.LUM_V_EVOLVED; } //$NON-NLS-1$ if( lc.startsWith("III") ) { return Planet.LUM_III; } //$NON-NLS-1$ if( lc.startsWith("II") ) { return Planet.LUM_II; } //$NON-NLS-1$ if( lc.startsWith("IV") ) { return Planet.LUM_IV; } //$NON-NLS-1$ if( lc.startsWith("Ia-0") ) { return Planet.LUM_0; } // Alias //$NON-NLS-1$ if( lc.startsWith("Ia0") ) { return Planet.LUM_0; } // Alias //$NON-NLS-1$ if( lc.startsWith("Ia+") ) { return Planet.LUM_0; } // Alias //$NON-NLS-1$ if( lc.startsWith("Iab") ) { return Planet.LUM_IAB; } //$NON-NLS-1$ if( lc.startsWith("Ia") ) { return Planet.LUM_IA; } //$NON-NLS-1$ if( lc.startsWith("Ib") ) { return Planet.LUM_IB; } //$NON-NLS-1$ if( lc.startsWith("I") ) { return Planet.LUM_I; } // includes Ia, Iab and Ib //$NON-NLS-1$ if( lc.startsWith("O") ) { return Planet.LUM_0; } //$NON-NLS-1$ if( lc.startsWith("VII") ) { return Planet.LUM_VII; } //$NON-NLS-1$ if( lc.startsWith("VI+") ) { return Planet.LUM_VI_PLUS; } //$NON-NLS-1$ if( lc.startsWith("VI") ) { return Planet.LUM_VI; } //$NON-NLS-1$ if( lc.startsWith("V") ) { return Planet.LUM_V; } //$NON-NLS-1$ return null; } public static String getPopulationRatingString(int pops) { if(pops < 0) { return "None"; } switch(pops) { case 0: return "Few"; case 1: return "Tens"; case 2: return "Hundreds"; case 3: return "Thousands"; case 4: return "Tens of thousands"; case 5: return "Hundreds of thousands"; case 6: return "Millions"; case 7: return "Tens of millions"; case 8: return "Hundreds of millions"; case 9: return "Billions"; case 10: return "Tens of billions"; case 11: return "Hundreds of billions"; case 12: return "Trillions"; default: return "Uncountable"; } } public static String getControlRatingString(int cr) { if(cr < 0) { return "in total anarchy"; } switch(cr) { case 0: return "in anarchy"; case 1: return "very free society"; case 2: return "free society"; case 3: return "moderately free society"; case 4: return "controlled society"; case 5: return "repressive"; case 6: return "under total control"; default: return "enslaved population"; } } public static String getHPGClass(Integer hpg) { if(null == hpg) { return null; } switch(hpg.intValue()) { case EquipmentType.RATING_A: return "A-rated HPG"; case EquipmentType.RATING_B: return "B-rated HPG"; case EquipmentType.RATING_C: return "C-rated Service"; case EquipmentType.RATING_D: return "D-rated Service"; case EquipmentType.RATING_X: return "none"; default: return "unknown"; } } public static String getIconImage(Planet planet) { if(!iconDataLoaded) { try(BufferedReader reader = new BufferedReader(new FileReader(new File("data", ICON_DATA_FILE)))) { String line; while(null != (line = reader.readLine())) { if(line.startsWith("#")) { // Ignore comments continue; } String[] parts = line.split("=", 2); if((null != parts) && (parts.length == 2)) { ICON_DATA.put(parts[0], parts[1]); } } iconDataLoaded = true; } catch (FileNotFoundException e) { MekHQ.logError(e); } catch (IOException e) { MekHQ.logError(e); } } return ICON_DATA.get(Utilities.nonNull(planet.getIcon(), "default")); } private StarUtil() {} }