001package com.fs.starfarer.api.util;
002
003import java.io.IOException;
004import java.lang.reflect.Method;
005import java.nio.Buffer;
006import java.text.DecimalFormat;
007import java.text.DecimalFormatSymbols;
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.Collection;
011import java.util.Collections;
012import java.util.Comparator;
013import java.util.HashMap;
014import java.util.HashSet;
015import java.util.LinkedHashMap;
016import java.util.LinkedHashSet;
017import java.util.List;
018import java.util.Locale;
019import java.util.Map;
020import java.util.Random;
021import java.util.Set;
022import java.util.UUID;
023import java.util.concurrent.atomic.AtomicLong;
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026
027import java.awt.Color;
028import java.awt.image.BufferedImage;
029import java.awt.image.Raster;
030
031import javax.imageio.ImageIO;
032
033import org.json.JSONArray;
034import org.json.JSONException;
035import org.json.JSONObject;
036import org.lwjgl.opengl.ATIMeminfo;
037import org.lwjgl.opengl.GL11;
038import org.lwjgl.opengl.NVXGpuMemoryInfo;
039import org.lwjgl.util.vector.Vector2f;
040import org.lwjgl.util.vector.Vector3f;
041
042import com.fs.starfarer.api.EveryFrameScript;
043import com.fs.starfarer.api.Global;
044import com.fs.starfarer.api.MusicPlayerPlugin;
045import com.fs.starfarer.api.campaign.AICoreAdminPlugin;
046import com.fs.starfarer.api.campaign.AICoreOfficerPlugin;
047import com.fs.starfarer.api.campaign.BattleAPI;
048import com.fs.starfarer.api.campaign.CampaignClockAPI;
049import com.fs.starfarer.api.campaign.CampaignFleetAPI;
050import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
051import com.fs.starfarer.api.campaign.CampaignUIAPI.CoreUITradeMode;
052import com.fs.starfarer.api.campaign.CargoAPI;
053import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
054import com.fs.starfarer.api.campaign.CargoStackAPI;
055import com.fs.starfarer.api.campaign.CommDirectoryEntryAPI;
056import com.fs.starfarer.api.campaign.CommDirectoryEntryAPI.EntryType;
057import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI;
058import com.fs.starfarer.api.campaign.FactionAPI;
059import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
060import com.fs.starfarer.api.campaign.FleetAssignment;
061import com.fs.starfarer.api.campaign.FleetInflater;
062import com.fs.starfarer.api.campaign.InteractionDialogAPI;
063import com.fs.starfarer.api.campaign.JumpPointAPI;
064import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination;
065import com.fs.starfarer.api.campaign.LocationAPI;
066import com.fs.starfarer.api.campaign.ParticleControllerAPI;
067import com.fs.starfarer.api.campaign.PlanetAPI;
068import com.fs.starfarer.api.campaign.PlanetSpecAPI;
069import com.fs.starfarer.api.campaign.RepLevel;
070import com.fs.starfarer.api.campaign.ReputationActionResponsePlugin.ReputationAdjustmentResult;
071import com.fs.starfarer.api.campaign.ResourceCostPanelAPI;
072import com.fs.starfarer.api.campaign.SectorEntityToken;
073import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
074import com.fs.starfarer.api.campaign.StarSystemAPI;
075import com.fs.starfarer.api.campaign.SubmarketPlugin;
076import com.fs.starfarer.api.campaign.SubmarketPlugin.OnClickAction;
077import com.fs.starfarer.api.campaign.TextPanelAPI;
078import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.EncounterOption;
079import com.fs.starfarer.api.campaign.ai.FleetAIFlags;
080import com.fs.starfarer.api.campaign.ai.ModularFleetAIAPI;
081import com.fs.starfarer.api.campaign.comm.CommMessageAPI.MessageClickAction;
082import com.fs.starfarer.api.campaign.econ.AbandonMarketPlugin;
083import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
084import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
085import com.fs.starfarer.api.campaign.econ.ImmigrationPlugin;
086import com.fs.starfarer.api.campaign.econ.Industry;
087import com.fs.starfarer.api.campaign.econ.MarketAPI;
088import com.fs.starfarer.api.campaign.econ.MarketAPI.SurveyLevel;
089import com.fs.starfarer.api.campaign.econ.MarketConditionAPI;
090import com.fs.starfarer.api.campaign.econ.StabilizeMarketPlugin;
091import com.fs.starfarer.api.campaign.econ.SubmarketAPI;
092import com.fs.starfarer.api.campaign.events.CampaignEventManagerAPI;
093import com.fs.starfarer.api.campaign.events.CampaignEventPlugin;
094import com.fs.starfarer.api.campaign.events.CampaignEventTarget;
095import com.fs.starfarer.api.campaign.rules.MemKeys;
096import com.fs.starfarer.api.campaign.rules.MemoryAPI;
097import com.fs.starfarer.api.characters.AbilityPlugin;
098import com.fs.starfarer.api.characters.MarketConditionSpecAPI;
099import com.fs.starfarer.api.characters.MutableCharacterStatsAPI;
100import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI;
101import com.fs.starfarer.api.characters.OfficerDataAPI;
102import com.fs.starfarer.api.characters.PersonAPI;
103import com.fs.starfarer.api.combat.CombatEngineAPI;
104import com.fs.starfarer.api.combat.CombatEntityAPI;
105import com.fs.starfarer.api.combat.DamageType;
106import com.fs.starfarer.api.combat.MissileAPI;
107import com.fs.starfarer.api.combat.MutableShipStatsAPI;
108import com.fs.starfarer.api.combat.ShipAPI;
109import com.fs.starfarer.api.combat.ShipAPI.HullSize;
110import com.fs.starfarer.api.combat.ShipCommand;
111import com.fs.starfarer.api.combat.ShipHullSpecAPI;
112import com.fs.starfarer.api.combat.ShipHullSpecAPI.ShipTypeHints;
113import com.fs.starfarer.api.combat.ShipVariantAPI;
114import com.fs.starfarer.api.combat.WeaponAPI;
115import com.fs.starfarer.api.combat.listeners.ApplyDamageResultAPI;
116import com.fs.starfarer.api.combat.listeners.CombatListenerUtil;
117import com.fs.starfarer.api.fleet.FleetMemberAPI;
118import com.fs.starfarer.api.graphics.SpriteAPI;
119import com.fs.starfarer.api.impl.SharedUnlockData;
120import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.CustomRepImpact;
121import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
122import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
123import com.fs.starfarer.api.impl.campaign.DModManager;
124import com.fs.starfarer.api.impl.campaign.JumpPointInteractionDialogPluginImpl;
125import com.fs.starfarer.api.impl.campaign.RuleBasedInteractionDialogPluginImpl;
126import com.fs.starfarer.api.impl.campaign.WarningBeaconEntityPlugin;
127import com.fs.starfarer.api.impl.campaign.abilities.ReversePolarityToggle;
128import com.fs.starfarer.api.impl.campaign.econ.impl.ConstructionQueue.ConstructionQueueItem;
129import com.fs.starfarer.api.impl.campaign.econ.impl.ShipQuality;
130import com.fs.starfarer.api.impl.campaign.econ.impl.ShipQuality.QualityData;
131import com.fs.starfarer.api.impl.campaign.events.BaseEventPlugin.MarketFilter;
132import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3;
133import com.fs.starfarer.api.impl.campaign.ids.Conditions;
134import com.fs.starfarer.api.impl.campaign.ids.Difficulties;
135import com.fs.starfarer.api.impl.campaign.ids.Drops;
136import com.fs.starfarer.api.impl.campaign.ids.Entities;
137import com.fs.starfarer.api.impl.campaign.ids.Factions;
138import com.fs.starfarer.api.impl.campaign.ids.HullMods;
139import com.fs.starfarer.api.impl.campaign.ids.Industries;
140import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
141import com.fs.starfarer.api.impl.campaign.ids.Personalities;
142import com.fs.starfarer.api.impl.campaign.ids.Stats;
143import com.fs.starfarer.api.impl.campaign.ids.Strings;
144import com.fs.starfarer.api.impl.campaign.ids.Submarkets;
145import com.fs.starfarer.api.impl.campaign.ids.Tags;
146import com.fs.starfarer.api.impl.campaign.ids.Terrain;
147import com.fs.starfarer.api.impl.campaign.intel.FactionCommissionIntel;
148import com.fs.starfarer.api.impl.campaign.intel.MessageIntel;
149import com.fs.starfarer.api.impl.campaign.intel.contacts.ContactIntel;
150import com.fs.starfarer.api.impl.campaign.plog.PlaythroughLog;
151import com.fs.starfarer.api.impl.campaign.plog.SModRecord;
152import com.fs.starfarer.api.impl.campaign.population.CoreImmigrationPluginImpl;
153import com.fs.starfarer.api.impl.campaign.procgen.ConditionGenDataSpec;
154import com.fs.starfarer.api.impl.campaign.procgen.DefenderDataOverride;
155import com.fs.starfarer.api.impl.campaign.procgen.PlanetConditionGenerator;
156import com.fs.starfarer.api.impl.campaign.procgen.PlanetGenDataSpec;
157import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData;
158import com.fs.starfarer.api.impl.campaign.procgen.StarAge;
159import com.fs.starfarer.api.impl.campaign.procgen.StarGenDataSpec;
160import com.fs.starfarer.api.impl.campaign.procgen.StarSystemGenerator;
161import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseThemeGenerator.OrbitGap;
162import com.fs.starfarer.api.impl.campaign.rulecmd.AddRemoveCommodity;
163import com.fs.starfarer.api.impl.campaign.rulecmd.unsetAll;
164import com.fs.starfarer.api.impl.campaign.submarkets.BaseSubmarketPlugin;
165import com.fs.starfarer.api.impl.campaign.submarkets.StoragePlugin;
166import com.fs.starfarer.api.impl.campaign.terrain.AsteroidSource;
167import com.fs.starfarer.api.impl.campaign.terrain.BaseTiledTerrain.TileParams;
168import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin;
169import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldParams;
170import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
171import com.fs.starfarer.api.impl.campaign.terrain.MagneticFieldTerrainPlugin;
172import com.fs.starfarer.api.impl.campaign.terrain.NebulaTerrainPlugin;
173import com.fs.starfarer.api.impl.campaign.terrain.PulsarBeamTerrainPlugin;
174import com.fs.starfarer.api.impl.campaign.terrain.StarCoronaTerrainPlugin;
175import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2;
176import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2.SlipstreamSegment;
177import com.fs.starfarer.api.impl.codex.CodexUnlocker;
178import com.fs.starfarer.api.loading.HullModSpecAPI;
179import com.fs.starfarer.api.loading.IndustrySpecAPI;
180import com.fs.starfarer.api.plugins.FactionPersonalityPickerPlugin;
181import com.fs.starfarer.api.plugins.SimulatorPlugin;
182import com.fs.starfarer.api.plugins.SurveyPlugin;
183import com.fs.starfarer.api.ui.Alignment;
184import com.fs.starfarer.api.ui.LabelAPI;
185import com.fs.starfarer.api.ui.TooltipMakerAPI;
186
187
188/**
189 * A random collection of utility methods.
190 * <p>
191 * Kotlin users: MagicLib contains extension methods for most of these functions.
192 */
193public class Misc {
194        
195        public static boolean CAN_SMOD_BUILT_IN = true;
196        
197        public static String SIR = "Sir";
198        public static String MAAM = "Ma'am";
199        public static String CAPTAIN = "Captain";
200        
201        public static float FLUX_PER_CAPACITOR = Global.getSettings().getFloat("fluxPerCapacitor");
202        public static float DISSIPATION_PER_VENT = Global.getSettings().getFloat("dissipationPerVent");
203        
204        private static boolean cbMode = Global.getSettings().getBoolean("colorblindMode");
205        
206        public static Color MOUNT_BALLISTIC = Global.getSettings().getColor("mountYellowColor");
207        public static Color MOUNT_MISSILE = Global.getSettings().getColor("mountGreenColor");
208        public static Color MOUNT_ENERGY = cbMode ? new Color(155,155,155,255) : Global.getSettings().getColor("mountBlueColor");
209        public static Color MOUNT_UNIVERSAL = Global.getSettings().getColor("mountGrayColor");
210        public static Color MOUNT_HYBRID = Global.getSettings().getColor("mountOrangeColor");
211        public static Color MOUNT_SYNERGY = Global.getSettings().getColor("mountCyanColor");
212        public static Color MOUNT_COMPOSITE = Global.getSettings().getColor("mountCompositeColor");
213        
214        // for combat entities
215        public static final int OWNER_NEUTRAL = 100;
216        public static final int OWNER_PLAYER = 0;
217        
218        public static Color FLOATY_EMP_DAMAGE_COLOR = new Color(255,255,255,255);
219        public static Color FLOATY_ARMOR_DAMAGE_COLOR = new Color(255,255,0,220);
220        public static Color FLOATY_SHIELD_DAMAGE_COLOR = new Color(200,200,255,220);
221        public static Color FLOATY_HULL_DAMAGE_COLOR = new Color(255,50,0,220);
222        
223//      public static final String SUPPLY_ACCESSIBILITY = "Supply Accessibility";
224        
225        public static float GATE_FUEL_COST_MULT = Global.getSettings().getFloat("gateTransitFuelCostMult");
226        
227        public static int MAX_COLONY_SIZE = Global.getSettings().getInt("maxColonySize");
228        public static int OVER_MAX_INDUSTRIES_PENALTY = Global.getSettings().getInt("overMaxIndustriesPenalty");
229        
230        
231        public static float FP_TO_BOMBARD_COST_APPROX_MULT = 12f;
232        public static float FP_TO_GROUND_RAID_STR_APPROX_MULT = 6f;
233        
234        public static String UNKNOWN = " ";
235        public static String UNSURVEYED = "??";
236        public static String PRELIMINARY = "?";
237        public static String FULL = "X";
238        
239        /**
240         * Name of "story points".
241         */
242        public static String STORY = "story";
243        
244        public static float MAX_OFFICER_LEVEL = Global.getSettings().getFloat("officerMaxLevel");
245        
246        public static Random random = new Random();
247
248        public static enum TokenType {
249                VARIABLE,
250                LITERAL,
251                OPERATOR,
252        }
253        
254        public static final Vector2f ZERO = new Vector2f(0, 0);
255        
256        public static class VarAndMemory {
257                public String name;
258                public MemoryAPI memory;
259        }
260        public static class Token {
261                public String string;
262                public TokenType type;
263                public String varNameWithoutMemoryKeyIfKeyIsValid = null;
264                public String varMemoryKey = null;
265                public Token(String string, TokenType type) {
266                        this.string = string;
267                        this.type = type;
268                        
269                        if (isVariable()) {
270                                int index = string.indexOf(".");
271                                if (index > 0 && index < string.length() - 1) {
272                                        varMemoryKey = string.substring(1, index);
273                                        varNameWithoutMemoryKeyIfKeyIsValid = "$" + string.substring(index + 1);
274                                }
275                        }
276                }
277
278                public VarAndMemory getVarNameAndMemory(Map<String, MemoryAPI> memoryMap) {
279                        String varName = varNameWithoutMemoryKeyIfKeyIsValid;
280                        MemoryAPI memory = memoryMap.get(varMemoryKey);
281                        if (memory == null) {
282                                varName = string;
283                                memory = memoryMap.get(MemKeys.LOCAL);
284                        }
285                        if (memory == null) {
286                                throw new RuleException("No memory found for keys: " + varMemoryKey + ", " + MemKeys.LOCAL);
287                        }
288                        
289                        VarAndMemory result = new VarAndMemory();
290                        result.name = varName;
291                        result.memory = memory;
292                        return result;
293                }
294                
295                public String getStringWithTokenReplacement(String ruleId, InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
296                        String text = getString(memoryMap);
297                        if (text == null) return null;
298                        text = Global.getSector().getRules().performTokenReplacement(ruleId, text, dialog.getInteractionTarget(), memoryMap);
299//                      Map<String, String> tokens = Global.getSector().getRules().getTokenReplacements(ruleId, dialog.getInteractionTarget(), memoryMap);
300//                      for (String token : tokens.keySet()) {
301//                              String value = tokens.get(token);
302//                              text = text.replaceAll("(?s)\\" + token, value);
303//                      }
304//                      text = Misc.replaceTokensFromMemory(text, memoryMap);
305                        return text;
306                }
307                public String getString(Map<String, MemoryAPI> memoryMap) {
308                        String string = null;
309                        if (isVariable()) {
310                                VarAndMemory var = getVarNameAndMemory(memoryMap);
311                                string = var.memory.getString(var.name);
312                        } else {
313                                string = this.string;
314                        }
315                        return string;
316                }
317                
318                public Object getObject(Map<String, MemoryAPI> memoryMap) {
319                        Object o = null;
320                        if (isVariable()) {
321                                VarAndMemory var = getVarNameAndMemory(memoryMap);
322                                o = var.memory.get(var.name);
323                        }
324                        return o;
325                }
326                
327                public boolean getBoolean(Map<String, MemoryAPI> memoryMap) {
328                        String str = getString(memoryMap);
329                        return Boolean.parseBoolean(str);
330                }
331                
332                public boolean isBoolean(Map<String, MemoryAPI> memoryMap) {
333                        String str = getString(memoryMap);
334                        return str.toLowerCase().equals("true") || str.toLowerCase().equals("false");
335                }
336                
337                public boolean isFloat(Map<String, MemoryAPI> memoryMap) {
338                        String str = null;
339                        if (isVariable()) {
340                                VarAndMemory var = getVarNameAndMemory(memoryMap);
341                                str = var.memory.getString(var.name);
342                        } else {
343                                str = string;
344                        }
345                        try {
346                                Float.parseFloat(str);
347                                return true;
348                        } catch (NumberFormatException e) {
349                                return false;
350                        }
351                }
352                
353                public float getFloat(Map<String, MemoryAPI> memoryMap) {
354                        float result = 0f;
355                        if (isVariable()) {
356                                VarAndMemory var = getVarNameAndMemory(memoryMap);
357                                result = var.memory.getFloat(var.name);
358                        } else {
359                                result = Float.parseFloat(string);
360                        }
361                        return result;
362                }
363                
364                public int getInt(Map<String, MemoryAPI> memoryMap) {
365                        return (int) Math.round(getFloat(memoryMap));
366                }
367                
368                public Color getColor(Map<String, MemoryAPI> memoryMap) {
369                        Object object = null;
370                        if (isVariable()) {
371                                VarAndMemory var = getVarNameAndMemory(memoryMap);
372                                object = var.memory.get(var.name);
373                        }
374                        if (object instanceof Color) {
375                                return (Color) object;
376                        }
377                                        
378                        String string = getString(memoryMap);
379                        try {
380                                String [] parts = string.split(Pattern.quote(","));
381                                return new Color(Integer.parseInt(parts[0]),
382                                                Integer.parseInt(parts[1]),
383                                                Integer.parseInt(parts[2]),
384                                                Integer.parseInt(parts[3]));
385                        } catch (Exception e) {
386                                if ("bad".equals(string)) {
387                                        string = "textEnemyColor";
388                                } else if ("good".equals(string)) {
389                                        string = "textFriendColor";
390                                } else if ("highlight".equals(string)) {
391                                        string = "buttonShortcut";
392                                } else if ("h".equals(string)) {
393                                        string = "buttonShortcut";
394                                } else if ("story".equals(string)) {
395                                        return Misc.getStoryOptionColor();
396                                } else if ("gray".equals(string)) {
397                                        return Misc.getGrayColor();
398                                } else if ("grey".equals(string)) {
399                                        return Misc.getGrayColor();
400                                } else {
401                                        FactionAPI faction = Global.getSector().getFaction(string);
402                                        if (faction != null) {
403                                                return faction.getBaseUIColor();
404                                        }
405                                }
406                                
407                                return Global.getSettings().getColor(string);
408                        }
409                }
410                
411                public boolean isLiteral() {
412                        return type == TokenType.LITERAL;
413                }
414                public boolean isVariable() {
415                        return type == TokenType.VARIABLE;
416                }
417                public boolean isOperator() {
418                        return type == TokenType.OPERATOR;
419                }
420                @Override
421                public String toString() {
422                        if (isVariable()) {
423                                return string + " (" + type.name() + ", memkey: " + varMemoryKey + ", name: " + varNameWithoutMemoryKeyIfKeyIsValid + ")";
424                        } else {
425                                return string + " (" + type.name() + ")";
426                        }
427                }
428        }
429        
430        public static List<Token> tokenize(String string) {
431                List<Token> result = new ArrayList<Token>();
432                boolean inQuote = false;
433                boolean inOperator = false;
434                
435                StringBuffer currToken = new StringBuffer();
436                for (int i = 0; i < string.length(); i++) {
437                        char curr = string.charAt(i);
438                        char next = 0;
439                        if (i + 1 < string.length()) next = string.charAt(i + 1);
440                        boolean charEscaped = false;
441                        if (curr == '\\') {
442                                i++;
443                                if (i >= string.length()) {
444                                        throw new RuleException("Escape character at end of string in: [" + string + "]");
445                                }
446                                curr = string.charAt(i);
447                                if (i + 1 < string.length()) next = string.charAt(i + 1);
448                                charEscaped = true;
449                        }
450//                      if (charEscaped) {
451//                              System.out.println("dfsdfs");
452//                      }
453                        
454                        if (curr == '"' && !charEscaped) {
455                                inQuote = !inQuote;
456                                if (!inQuote && currToken.length() <= 0) {
457                                        result.add(new Token("", TokenType.LITERAL));
458                                } else if (currToken.length() > 0) {
459                                        String str = currToken.toString();
460                                        if (!inQuote) {
461                                                result.add(new Token(str, TokenType.LITERAL));
462                                        } else {
463                                                if (str.startsWith("$")) {
464                                                        result.add(new Token(str, TokenType.VARIABLE));
465                                                } else if (inOperator) {
466                                                        result.add(new Token(str, TokenType.OPERATOR));
467                                                } else {
468                                                        result.add(new Token(str, TokenType.LITERAL));
469                                                }
470                                        }
471                                }
472                                inOperator = false;
473                                currToken.delete(0, 1000000);
474                                continue;
475                        }
476                        
477                        if (!inQuote && (curr == ' ' || curr == '\t')) {
478                                if (currToken.length() > 0) {
479                                        String str = currToken.toString();
480                                        if (str.startsWith("$")) {
481                                                result.add(new Token(str, TokenType.VARIABLE));
482                                        } else if (inOperator) {
483                                                result.add(new Token(str, TokenType.OPERATOR));
484                                        } else {
485                                                result.add(new Token(str, TokenType.LITERAL));
486                                        }
487                                }
488                                inOperator = false;
489                                currToken.delete(0, 1000000);
490                                continue;
491                        }
492                        
493                        if (!inQuote && !inOperator && isOperatorChar(curr) && (curr != '-' || !isDigit(next))) {
494                                if (currToken.length() > 0) {
495                                        String str = currToken.toString();
496                                        if (str.startsWith("$")) {
497                                                result.add(new Token(str, TokenType.VARIABLE));
498                                        } else {
499                                                result.add(new Token(str, TokenType.LITERAL));
500                                        }
501                                }
502                                currToken.delete(0, 1000000);
503                                inOperator = true;
504                                if (charEscaped && curr == 'n') {
505                                        currToken.append("\n");
506                                } else {
507                                        currToken.append(curr);
508                                }
509                                continue;
510                        }
511                        
512                        if (!inQuote && inOperator && !isOperatorChar(curr)) {
513                                if (currToken.length() > 0) {
514                                        String str = currToken.toString();
515                                        result.add(new Token(str, TokenType.OPERATOR));
516                                }
517                                currToken.delete(0, 1000000);
518                                inOperator = false;
519                                if (charEscaped && curr == 'n') {
520                                        currToken.append("\n");
521                                } else {
522                                        currToken.append(curr);
523                                }
524                                continue;
525                        }
526                        
527                        if (charEscaped && curr == 'n') {
528                                currToken.append("\n");
529                        } else {
530                                currToken.append(curr);
531                        }
532                }
533                
534                if (inQuote) {
535                        throw new RuleException("Unmatched quotes in string: " + string + "]");
536                }
537                
538                if (currToken.length() > 0) {
539                        String str = currToken.toString();
540                        if (str.startsWith("$")) {
541                                result.add(new Token(str, TokenType.VARIABLE));
542                        } else if (inOperator) {
543                                result.add(new Token(str, TokenType.OPERATOR));
544                        } else {
545                                result.add(new Token(str, TokenType.LITERAL));
546                        }
547                }
548        
549                return result;
550        }
551        
552        
553        private static boolean isDigit(char c) {
554                if (c == 0) return false;
555                String digits = "1234567890";
556                return digits.contains("" + c);
557        }
558        private static boolean isOperatorChar(char c) {
559                String operatorChars = "=<>!+-";
560                return operatorChars.contains("" + c);
561        }
562        
563        
564        public static String ucFirst(String str) {
565                if (str == null) return "Null";
566                if (str.isEmpty()) return "";
567                return ("" + str.charAt(0)).toUpperCase() + str.substring(1);
568        }
569        
570        public static String lcFirst(String str) {
571                if (str == null) return "Null";
572                if (str.isEmpty()) return "";
573                return ("" + str.charAt(0)).toLowerCase() + str.substring(1);
574        }
575        
576        
577        public static String replaceTokensFromMemory(String text, Map<String, MemoryAPI> memoryMap) {
578                List<String> keySet = new ArrayList<String>(memoryMap.keySet());
579                if (keySet.contains(MemKeys.LOCAL)) {
580                        keySet.remove(MemKeys.LOCAL);
581                        keySet.add(0, MemKeys.LOCAL);
582                }
583                for (String key : keySet) {
584                        MemoryAPI memory = memoryMap.get(key);
585                        List<String> keys = new ArrayList<String>(memory.getKeys());
586                        Collections.sort(keys, new Comparator<String>() {
587                                public int compare(String o1, String o2) {
588                                        return o2.length() - o1.length();
589                                }
590                        });
591                        for (String token : keys) {
592                                Object value = memory.get(token);
593                                if (value == null) value = "null";
594                                if (value instanceof String || value instanceof Boolean || value instanceof Float || value instanceof Integer) {
595                                        text = text.replaceAll("(?s)\\$" + Pattern.quote(key) + "\\." + Pattern.quote(token.substring(1)), value.toString());
596                                        text = text.replaceAll("(?s)\\$" + Pattern.quote(token.substring(1)), value.toString());
597                                }
598                        }
599                }
600                return text;
601        }
602        
603        
604        public static float getDistance(SectorEntityToken from, SectorEntityToken to) {
605                return getDistance(from.getLocation(), to.getLocation());
606        }
607        public static float getDistanceLY(SectorEntityToken from, SectorEntityToken to) {
608                return getDistanceLY(from.getLocationInHyperspace(), to.getLocationInHyperspace());
609        }
610        
611        
612        private static Vector2f temp3 = new Vector2f();
613        public static float getDistance(Vector2f v1, Vector2f v2) {
614                //return Vector2f.sub(v1, v2, temp3).length();
615                return (float) Math.sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y));
616        }
617        
618        public static float getDistanceSq(Vector2f v1, Vector2f v2) {
619                //return Vector2f.sub(v1, v2, temp3).lengthSquared();
620                return (v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y);
621        }
622        
623        public static float getDistance(float x1, float y1, float x2, float y2) {
624//              float xDiff = Math.abs(x1 - x2);
625//              float yDiff = Math.abs(y1 - y2);
626//              return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
627                return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
628                
629        }
630        
631        public static float getDistanceToPlayerLY(Vector2f locInHyper) {
632                if (Global.getSector().getPlayerFleet() == null) return 100000f;
633                return getDistanceLY(Global.getSector().getPlayerFleet().getLocationInHyperspace(), locInHyper);
634        }
635        public static float getDistanceToPlayerLY(SectorEntityToken other) {
636                if (Global.getSector().getPlayerFleet() == null) return 100000f;
637                return getDistanceLY(Global.getSector().getPlayerFleet().getLocationInHyperspace(), other.getLocationInHyperspace());
638        }
639        
640        public static float getDistanceLY(Vector2f v1, Vector2f v2) {
641                return Vector2f.sub(v1, v2, temp3).length() / getUnitsPerLightYear();
642        }
643        
644        public static float getRounded(float in) {
645                if (in <= 10) return Math.max(1, (int) in);
646                float pow = (int) Math.log10(in);
647                float div = (float) Math.pow(10, Math.max(0, pow - 1));
648                if (pow == 1) div = 10;
649                return (int) Math.round(in / div) * div;
650        }
651        
652        public static String getRoundedValue(float value) {
653                if (Math.abs((float)Math.round(value) - value) < 0.0001f) {
654                        //if (value > 10 || value < -10) {
655                                return String.format("%d", (int) Math.round(value));
656                        //} else {
657                                //return String.format("%.1f", value);
658                        //}
659                } else if ((int) Math.round((value * 100f)) == (int) Math.round((value * 10f)) * 10) {
660                        return (value > 10 || value < -10) ? "" + (int) Math.round(value) : String.format("%.1f", value);
661                } else {
662                        return (value > 10 || value < -10) ? "" + (int) Math.round(value) : String.format("%.2f", value);
663                }
664        }
665        
666        public static float getRoundedValueFloat(float value) {
667                if (Math.abs((float)Math.round(value) - value) < 0.0001f) {
668                        return (int) Math.round(value);
669                } else if ((int) Math.round((value * 100f)) == (int) Math.round((value * 10f)) * 10) {
670                        return (value > 10 || value < -10) ? (int) Math.round(value) :
671                                                (Math.round(value * 10f) / 10f);
672                } else {
673                        return (value > 10 || value < -10) ? (int) Math.round(value) :
674                                                (Math.round(value * 100f) / 100f);
675                }
676        }
677        
678        public static String getRoundedValueMaxOneAfterDecimal(float value) {
679                if (Math.abs((float)Math.round(value) - value) < 0.0001f) {
680                        return String.format("%d", (int) Math.round(value));
681                } else if ((int) Math.round((value * 100f)) == (int) Math.round((value * 10f)) * 10) {
682                        return (value >= 10 || value <= -10) ? "" + (int) Math.round(value) : String.format("%.1f", value);
683                } else {
684                        return (value >= 10 || value <= -10) ? "" + (int) Math.round(value) : String.format("%.1f", value);
685                }
686        }
687        
688        public static String getRoundedValueOneAfterDecimalIfNotWhole(float value) {
689                if (Math.abs((float)Math.round(value) - value) < 0.0001f) {
690                        return String.format("%d", (int) Math.round(value));
691                } else {
692                        return String.format("%.1f", value);
693                }
694        }
695        
696        
697
698        public static float logOfBase(float base, float num) {
699            return (float) (Math.log(num) / Math.log(base));
700        }
701
702        public static Vector2f getPointAtRadius(Vector2f from, float r) {
703                float angle = (float) ((float) Math.random() * Math.PI * 2f);
704                float x = (float) (Math.cos(angle) * r) + from.x;
705                float y = (float) (Math.sin(angle) * r) + from.y;
706                return new Vector2f(x, y);
707        }
708        
709        public static Vector2f getPointAtRadius(Vector2f from, float r, Random random) {
710                float angle = (float) (random.nextFloat() * Math.PI * 2f);
711                float x = (float) (Math.cos(angle) * r) + from.x;
712                float y = (float) (Math.sin(angle) * r) + from.y;
713                return new Vector2f(x, y);
714        }
715        
716        public static Vector2f getPointWithinRadius(Vector2f from, float r) {
717                return getPointWithinRadius(from, r, random);
718        }
719        public static Vector2f getPointWithinRadius(Vector2f from, float r, Random random) {
720                r = r * random.nextFloat();
721                float angle = (float) (random.nextFloat() * Math.PI * 2f);
722                float x = (float) (Math.cos(angle) * r) + from.x;
723                float y = (float) (Math.sin(angle) * r) + from.y;
724                return new Vector2f(x, y);
725        }
726        
727        public static Vector2f getPointWithinRadiusUniform(Vector2f from, float r, Random random) {
728                r = (float) (r * Math.sqrt(random.nextFloat()));
729                float angle = (float) (random.nextFloat() * Math.PI * 2f);
730                float x = (float) (Math.cos(angle) * r) + from.x;
731                float y = (float) (Math.sin(angle) * r) + from.y;
732                return new Vector2f(x, y);
733        }
734        
735        public static Vector2f getPointWithinRadiusUniform(Vector2f from, float minR, float maxR, Random random) {
736                float r = (float) (minR + (maxR - minR) * Math.sqrt(random.nextFloat()));
737                float angle = (float) (random.nextFloat() * Math.PI * 2f);
738                float x = (float) (Math.cos(angle) * r) + from.x;
739                float y = (float) (Math.sin(angle) * r) + from.y;
740                return new Vector2f(x, y);
741        }
742        
743        public static float getSnapshotFPLost(CampaignFleetAPI fleet) {
744                float fp = fleet.getFleetPoints();
745                float before = 0;
746                for (FleetMemberAPI member : fleet.getFleetData().getSnapshot()) {
747                        before += member.getFleetPointCost();
748                }
749                
750                return before - fp;
751        }
752        
753        public static List<FleetMemberAPI> getSnapshotMembersLost(CampaignFleetAPI fleet) {
754                List<FleetMemberAPI> lost = new ArrayList<FleetMemberAPI>();
755                List<FleetMemberAPI> curr = fleet.getFleetData().getMembersListCopy();
756                for (FleetMemberAPI member : fleet.getFleetData().getSnapshot()) {
757                        if (!curr.contains(member)) {
758                                lost.add(member);
759                        }
760                }
761                
762                return lost;
763        }
764        
765        
766        public static CampaignEventPlugin startEvent(CampaignEventTarget eventTarget, String eventId, Object params) {
767                CampaignEventManagerAPI manager = Global.getSector().getEventManager();
768                CampaignEventPlugin event = manager.getOngoingEvent(eventTarget, eventId);
769                if (event == null) {
770                        event = manager.startEvent(eventTarget, eventId, params);
771                }
772                return event;
773        }
774        
775        public static Color getStoryDarkBrighterColor() {
776                return setAlpha(scaleColorOnly(getStoryOptionColor(), 0.65f), 255);
777        }
778        public static Color getStoryDarkColor() {
779                return setAlpha(scaleColorOnly(getStoryOptionColor(), 0.4f), 175);
780        }
781        public static Color getStoryBrightColor() {
782                Color bright = interpolateColor(getStoryOptionColor(), 
783                                setAlpha(Color.white, 255),
784                                0.35f);
785                return bright;
786        }
787        public static Color getStoryOptionColor() {
788                //return Misc.interpolateColor(Misc.getButtonTextColor(), Misc.getPositiveHighlightColor(), 0.5f);
789                return Global.getSettings().getColor("storyOptionColor");
790                //return Global.getSettings().getColor("tooltipTitleAndLightHighlightColor");
791        }
792        
793        public static Color getHighlightedOptionColor() {
794                return Global.getSettings().getColor("buttonShortcut");
795        }
796        
797        public static Color getHighlightColor() {
798                return Global.getSettings().getColor("buttonShortcut");
799        }
800        public static Color getDarkHighlightColor() {
801                Color hc = Misc.getHighlightColor();
802                return Misc.setAlpha(hc, 255);
803        }
804        public static Color getTooltipTitleAndLightHighlightColor() {
805                return Global.getSettings().getColor("tooltipTitleAndLightHighlightColor");
806        }
807        public static Color getNegativeHighlightColor() {
808                if (Global.getSettings().getBoolean("colorblindMode")) {
809                        return new Color(0, 100, 255);
810                }
811                return Global.getSettings().getColor("textEnemyColor");
812        }
813        
814        public static Color getBallisticMountColor() {
815                return Global.getSettings().getColor("mountYellowColor");
816        }
817        public static Color getMissileMountColor() {
818                return Global.getSettings().getColor("mountGreenColor");
819        }
820        public static Color getEnergyMountColor() {
821                if (Global.getSettings().getBoolean("colorblindMode")) {
822                        return new Color(155,155,155,255);
823                }
824                return Global.getSettings().getColor("mountBlueColor");
825        }
826        
827        public static Color getPositiveHighlightColor() {
828                return Global.getSettings().getColor("textFriendColor");
829        }
830        
831        public static Color getGrayColor() {
832                return Global.getSettings().getColor("textGrayColor");
833        }
834        
835        public static Color getBrightPlayerColor() {
836                return Global.getSector().getPlayerFaction().getBrightUIColor();
837        }
838        public static Color getBasePlayerColor() {
839                return Global.getSector().getPlayerFaction().getBaseUIColor();
840        }
841        public static Color getDarkPlayerColor() {
842                return Global.getSector().getPlayerFaction().getDarkUIColor();
843        }
844        public static Color getTextColor() {
845                return Global.getSettings().getColor("standardTextColor");
846        }
847        public static Color getButtonTextColor() {
848                return Global.getSettings().getColor("buttonText");
849        }
850        
851        public static float getUnitsPerLightYear() {
852                return Global.getSettings().getFloat("unitsPerLightYear");
853        }
854        
855        public static float getProfitMarginFlat() {
856                return Global.getSettings().getFloat("profitMarginFlat");
857        }
858        
859        public static float getProfitMarginMult() {
860                return Global.getSettings().getFloat("profitMarginMult");
861        }
862        
863        public static float getEconomyInterval() {
864                return Global.getSettings().getFloat("economyIntervalnGameDays");
865        }
866        
867        public static float getGenericRollingAverageFactor() {
868                return Global.getSettings().getFloat("genericRollingAverageFactor");
869        }
870        
871        public static IntervalUtil createEconIntervalTracker() {
872                float interval = getEconomyInterval();
873                return new IntervalUtil(interval * 0.75f, interval * 1.25f);
874        }
875        
876        public static String getAndJoined(List<String> strings) {
877                return getAndJoined(strings.toArray(new String [0]));
878        }
879        
880        public static String getAndJoined(String ... strings) {
881                return getJoined("and", strings);
882        }
883        
884        public static String getJoined(String joiner, List<String> strings) {
885                return getJoined(joiner, strings.toArray(new String [0]));
886        }
887        public static String getJoined(String joiner, String ... strings) {
888                if (strings.length == 1) return strings[0];
889                
890                String result = "";
891                for (int i = 0; i < strings.length - 1; i++) {
892                        result += strings[i] + ", ";
893                }
894                if (!result.isEmpty()) {
895                        result = result.substring(0, result.length() - 2);
896                }
897                if (strings.length > 2) {
898                        if (joiner.isEmpty()) {
899                                result += ", " + strings[strings.length - 1];
900                        } else {
901                                result += ", " + joiner + " " + strings[strings.length - 1];
902                        }
903                } else if (strings.length == 2) {
904                        if (joiner.isEmpty()) {
905                                result += ", " + strings[strings.length - 1];
906                        } else {
907                                result += " " + joiner + " " + strings[strings.length - 1];
908                        }
909                }
910                return result;
911        }
912        
913        public static interface FleetFilter {
914                boolean accept(CampaignFleetAPI curr);
915        }
916        
917        public static List<CampaignFleetAPI> findNearbyFleets(SectorEntityToken from, float maxRange, FleetFilter filter) {
918                List<CampaignFleetAPI> result = new ArrayList<CampaignFleetAPI>();
919                for (CampaignFleetAPI fleet : from.getContainingLocation().getFleets()) {
920                        if (fleet == from) continue;
921                        float dist = Misc.getDistance(fleet.getLocation(), from.getLocation());
922                        if (dist > maxRange) continue;
923                        
924                        if (filter == null || filter.accept(fleet)) {
925                                result.add(fleet);
926                        }
927                }
928                return result;
929        }
930        
931        public static List<CampaignFleetAPI> getFleetsInOrNearSystem(StarSystemAPI system) {
932                List<CampaignFleetAPI> result = new ArrayList<CampaignFleetAPI>(system.getFleets());
933                for (CampaignFleetAPI fleet : Global.getSector().getHyperspace().getFleets()) {
934                        if (!fleet.isInOrNearSystem(system)) continue;
935                        result.add(fleet);
936                }
937                return result;
938        }
939        
940        
941        public static List<MarketAPI> getMarketsInLocation(LocationAPI location, String factionId) {
942                List<MarketAPI> result = new ArrayList<MarketAPI>();
943                for (MarketAPI curr : getMarketsInLocation(location)) {
944                        if (curr.getFactionId().equals(factionId)) {
945                                result.add(curr);
946                        }
947                }
948                return result;
949        }
950        
951        public static MarketAPI getBiggestMarketInLocation(LocationAPI location) {
952                int max = 0;
953                MarketAPI best = null;
954                for (MarketAPI curr : getMarketsInLocation(location)) {
955                        int size = curr.getSize();
956                        if (size > max || (size == max && curr.getFaction().isPlayerFaction())) {
957                                max = size;
958                                best = curr;
959                        }
960                }
961                return best;
962        }
963        
964        
965        public static List<MarketAPI> getMarketsInLocation(LocationAPI location) {
966                if (location == null) return new ArrayList<MarketAPI>();
967                return Global.getSector().getEconomy().getMarkets(location);
968//              List<MarketAPI> result = new ArrayList<MarketAPI>();
969//              for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
970//                      if (market.getContainingLocation() != location) continue;
971//                      result.add(market);
972//              }
973//              return result;
974        }
975        
976        public static List<MarketAPI> getFactionMarkets(FactionAPI faction, String econGroup) {
977                List<MarketAPI> result = new ArrayList<MarketAPI>();
978                for (MarketAPI market : Global.getSector().getEconomy().getMarketsInGroup(econGroup)) {
979                        if (market.getFaction() == faction) {
980                                result.add(market);
981                        }
982                }
983                return result;
984        }
985        public static List<MarketAPI> getPlayerMarkets(boolean includeNonPlayerFaction) {
986                FactionAPI player = Global.getSector().getFaction(Factions.PLAYER);
987                List<MarketAPI> result = new ArrayList<MarketAPI>();
988                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
989                        if (market.getFaction() == player) {
990                                result.add(market);
991                        } else if (includeNonPlayerFaction && market.isPlayerOwned()) {
992                                result.add(market);
993                        }
994                }
995                return result;
996        }
997        
998        public static List<StarSystemAPI> getPlayerSystems(boolean includeNonPlayerFaction) {
999                return getSystemsWithPlayerColonies(includeNonPlayerFaction);
1000        }
1001        public static List<StarSystemAPI> getSystemsWithPlayerColonies(boolean includeNonPlayerFaction) {
1002                List<MarketAPI> markets = Misc.getPlayerMarkets(includeNonPlayerFaction);
1003                List<StarSystemAPI> systems = new ArrayList<StarSystemAPI>();
1004                for (MarketAPI market : markets) {
1005                        StarSystemAPI system = market.getStarSystem();
1006                        if (system != null && !systems.contains(system)) {
1007                                systems.add(system);
1008                        }
1009                }
1010                return systems;
1011        }
1012        
1013        public static List<MarketAPI> getFactionMarkets(String factionId) {
1014                return getFactionMarkets(Global.getSector().getFaction(factionId));
1015        }
1016        public static List<MarketAPI> getFactionMarkets(FactionAPI faction) {
1017                //Global.getSector().getEconomy().get
1018                List<MarketAPI> result = new ArrayList<MarketAPI>();
1019                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
1020                        if (market.getFaction() == faction) {
1021                                result.add(market);
1022                        }
1023                }
1024                return result;
1025        }
1026        
1027        public static List<MarketAPI> getNearbyMarkets(Vector2f locInHyper, float distLY) {
1028                List<MarketAPI> result = new ArrayList<MarketAPI>();
1029                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
1030                        float dist = getDistanceLY(market.getLocationInHyperspace(), locInHyper);
1031                        if (dist > distLY) continue;
1032                        result.add(market);
1033                }
1034                return result;
1035        }
1036        
1037        public static int getNumHostileMarkets(FactionAPI faction, SectorEntityToken from, float maxDist) {
1038                int hostileMarketsNearPoint = 0;
1039                for (MarketAPI market : Misc.getMarketsInLocation(from.getContainingLocation())) {
1040                        SectorEntityToken primary = market.getPrimaryEntity();
1041                        float dist = getDistance(primary.getLocation(), from.getLocation());
1042                        if (dist > maxDist) continue;
1043                        if (market.getFaction() != null && market.getFaction().isHostileTo(faction)) {
1044                                hostileMarketsNearPoint ++;
1045                        }
1046                }
1047                return hostileMarketsNearPoint;
1048        }
1049        
1050        public static List<StarSystemAPI> getNearbyStarSystems(SectorEntityToken token, float maxRangeLY) {
1051                List<StarSystemAPI> result = new ArrayList<StarSystemAPI>();
1052                
1053                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
1054                        float dist = Misc.getDistanceLY(token.getLocationInHyperspace(), system.getLocation());
1055                        if (dist > maxRangeLY) continue;
1056                        result.add(system);
1057                }
1058                return result;
1059        }
1060        
1061        public static StarSystemAPI getNearbyStarSystem(SectorEntityToken token, float maxRangeLY) {
1062                if (token.getContainingLocation() instanceof StarSystemAPI) {
1063                        return (StarSystemAPI) token.getContainingLocation();
1064                }
1065                
1066                StarSystemAPI closest = null;
1067                float minDist = Float.MAX_VALUE;
1068                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
1069                        float dist = Misc.getDistanceLY(token.getLocationInHyperspace(), system.getLocation());
1070                        if (dist > maxRangeLY) continue;
1071                        if (dist < minDist) {
1072                                minDist = dist;
1073                                closest = system;
1074                        }
1075                        //return system;
1076                }
1077                return closest;
1078        }
1079        
1080        public static StarSystemAPI getNearestStarSystem(SectorEntityToken token) {
1081                if (token.getContainingLocation() instanceof StarSystemAPI) {
1082                        return (StarSystemAPI) token.getContainingLocation();
1083                }
1084                
1085                float minDist = Float.MAX_VALUE;
1086                StarSystemAPI closest = null;
1087                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
1088                        float dist = Misc.getDistanceLY(token.getLocationInHyperspace(), system.getLocation());
1089                        if (dist < minDist) {
1090                                minDist = dist;
1091                                closest = system;
1092                        }
1093                }
1094                return closest;
1095        }
1096        
1097        public static StarSystemAPI getNearbyStarSystem(SectorEntityToken token) {
1098                if (token.getContainingLocation() instanceof StarSystemAPI) {
1099                        return (StarSystemAPI) token.getContainingLocation();
1100                }
1101                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
1102                        if (token.isInOrNearSystem(system)) return system;
1103                }
1104                return null;
1105        }
1106        
1107        
1108        public static boolean showRuleDialog(SectorEntityToken entity, String initialTrigger) {
1109                RuleBasedInteractionDialogPluginImpl plugin;
1110                if (initialTrigger != null) {
1111                        plugin = new RuleBasedInteractionDialogPluginImpl(initialTrigger);
1112                } else {
1113                        plugin = new RuleBasedInteractionDialogPluginImpl();
1114                }
1115                return Global.getSector().getCampaignUI().showInteractionDialog(plugin, entity);
1116        }
1117        
1118        public static float DEG_PER_RAD = 180f / 3.1415926f;
1119        
1120        public static float getAngleInDegreesStrict(Vector2f v) {
1121                float angle = (float) Math.atan2(v.y, v.x) * DEG_PER_RAD;
1122                return angle;
1123        }
1124        
1125        public static float getAngleInDegreesStrict(Vector2f from, Vector2f to) {
1126                float dx = to.x - from.x;
1127                float dy = to.y - from.y;
1128                float angle = (float) Math.atan2(dy, dx) * DEG_PER_RAD;
1129                return angle;
1130        }
1131        public static float getAngleInDegrees(Vector2f v) {
1132                return Global.getSettings().getAngleInDegreesFast(v);
1133        }
1134        
1135        public static float getAngleInDegrees(Vector2f from, Vector2f to) {
1136                return Global.getSettings().getAngleInDegreesFast(from, to);
1137        }
1138        
1139        public static Vector2f normalise(Vector2f v) {
1140                if (v.lengthSquared() > Float.MIN_VALUE) {
1141                        return (Vector2f)v.normalise();
1142                }
1143                //return v;
1144                return new Vector2f(1, 0);
1145        }
1146        
1147        public static float normalizeAngle(float angleDeg) {
1148                return (angleDeg % 360f + 360f) % 360f;
1149        }
1150        
1151        public static MarketAPI findNearestLocalMarket(SectorEntityToken token, float maxDist, MarketFilter filter) {
1152                List<MarketAPI> localMarkets = getMarketsInLocation(token.getContainingLocation());
1153                float distToLocalMarket = Float.MAX_VALUE;
1154                MarketAPI closest = null;
1155                for (MarketAPI market : localMarkets) {
1156                        if (filter != null && !filter.acceptMarket(market)) continue;
1157                        
1158                        if (market.getPrimaryEntity() == null) continue;
1159                        if (market.getPrimaryEntity().getContainingLocation() != token.getContainingLocation()) continue;
1160                        
1161                        float currDist = Misc.getDistance(market.getPrimaryEntity().getLocation(), token.getLocation());
1162                        if (currDist > maxDist) continue;
1163                        if (currDist < distToLocalMarket) {
1164                                distToLocalMarket = currDist;
1165                                closest = market;
1166                        }
1167                }
1168                return closest;
1169        }
1170        public static List<MarketAPI> findNearbyLocalMarkets(SectorEntityToken token, float maxDist, MarketFilter filter) {
1171                List<MarketAPI> localMarkets = getMarketsInLocation(token.getContainingLocation());
1172                List<MarketAPI> result = new ArrayList<MarketAPI>();
1173                
1174                for (MarketAPI market : localMarkets) {
1175                        if (filter != null && !filter.acceptMarket(market)) continue;
1176                        if (market.getPrimaryEntity() == null) continue;
1177                        if (market.getPrimaryEntity().getContainingLocation() != token.getContainingLocation()) continue;
1178                        
1179                        float currDist = Misc.getDistance(market.getPrimaryEntity().getLocation(), token.getLocation());
1180                        if (currDist > maxDist) continue;
1181                        
1182                        result.add(market);
1183                        
1184                }
1185                return result;
1186        }
1187        
1188        public static MarketAPI findNearestLocalMarketWithSameFaction(final SectorEntityToken token, float maxDist) {
1189                return findNearestLocalMarket(token, maxDist, new MarketFilter() {
1190                        public boolean acceptMarket(MarketAPI curr) {
1191                                return curr.getFaction() == token.getFaction();
1192                        }
1193                });
1194        }
1195        
1196        public static Vector2f getUnitVector(Vector2f from, Vector2f to) {
1197                return getUnitVectorAtDegreeAngle(getAngleInDegrees(from, to));
1198        }
1199        
1200        public static float RAD_PER_DEG = 0.01745329251f;
1201        public static Vector2f getUnitVectorAtDegreeAngle(float degrees) {
1202                Vector2f result = new Vector2f();
1203                float radians = degrees * RAD_PER_DEG;
1204                result.x = (float)Math.cos(radians);
1205                result.y = (float)Math.sin(radians);
1206                
1207                return result;
1208        }
1209        
1210        public static Vector2f rotateAroundOrigin(Vector2f v, float angle) {
1211                float cos = (float) Math.cos(angle * RAD_PER_DEG);
1212                float sin = (float) Math.sin(angle * RAD_PER_DEG);
1213                Vector2f r = new Vector2f();
1214                r.x = v.x * cos - v.y * sin;
1215                r.y = v.x * sin + v.y * cos;
1216                return r;
1217        }
1218        
1219        public static Vector2f rotateAroundOrigin(Vector2f v, float angle, Vector2f origin) {
1220                float cos = (float) Math.cos(angle * RAD_PER_DEG);
1221                float sin = (float) Math.sin(angle * RAD_PER_DEG);
1222                Vector2f r = Vector2f.sub(v, origin, new Vector2f());
1223                Vector2f r2 = new Vector2f();
1224                r2.x = r.x * cos - r.y * sin;
1225                r2.y = r.x * sin + r.y * cos;
1226                Vector2f.add(r2, origin, r2);
1227                return r2;
1228        }
1229        
1230        /**
1231         * Angles.
1232         * @param one
1233         * @param two
1234         * @param check
1235         * @return
1236         */
1237        public static boolean isBetween(float one, float two, float check) {
1238                one = normalizeAngle(one);
1239                two = normalizeAngle(two);
1240                check = normalizeAngle(check);
1241
1242                //System.out.println(one + "," + two + "," + check);
1243                if (check >= one && check <= two) return true;
1244                
1245                if (one > two) {
1246                        if (check <= two) return true;
1247                        if (check >= one) return true;
1248                }
1249                return false;
1250        }
1251        
1252        public static float getShieldedCargoFraction(CampaignFleetAPI fleet) {
1253                float shielded = 0f;
1254                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
1255                        if (member.isMothballed()) continue;
1256                        if (member.getVariant().hasHullMod(HullMods.SHIELDED_CARGO_HOLDS)) {
1257                                shielded += member.getCargoCapacity();
1258                        }
1259                }
1260                float max = fleet.getCargo().getMaxCapacity();
1261                if (max < 1) return 0f;
1262                return shielded / max;
1263        }
1264        
1265        
1266        public static Color interpolateColor(Color from, Color to, float progress) {
1267                float red = (float)from.getRed() + ((float)to.getRed() - (float)from.getRed()) * progress;
1268                float green = (float)from.getGreen() + ((float)to.getGreen() - (float)from.getGreen()) * progress;
1269                float blue = (float)from.getBlue() + ((float)to.getBlue() - (float)from.getBlue()) * progress;
1270                float alpha = (float)from.getAlpha() + ((float)to.getAlpha() - (float)from.getAlpha()) * progress;
1271                red = Math.round(red);
1272                green = Math.round(green);
1273                blue = Math.round(blue);
1274                alpha = Math.round(alpha);
1275                return new Color((int)red, (int)green, (int)blue, (int)alpha);
1276        }
1277        
1278        public static Color genColor(Color min, Color max, Random random) {
1279                Color color = new Color((int) (min.getRed() + (max.getRed() - min.getRed()) * random.nextDouble()),
1280                                (int) (min.getGreen() + (max.getGreen() - min.getGreen()) * random.nextDouble()),
1281                                (int) (min.getBlue() + (max.getBlue() - min.getBlue()) * random.nextDouble()),
1282                                255);
1283                
1284                return color;
1285        }
1286        
1287        public static Vector2f interpolateVector(Vector2f from, Vector2f to, float progress) {
1288                Vector2f v = new Vector2f(from);
1289                
1290                v.x += (to.x - from.x) * progress;
1291                v.y += (to.y - from.y) * progress;
1292                
1293                return v;
1294        }
1295        
1296        public static float interpolate(float from, float to, float progress) {
1297                to = from + (to - from) * progress;
1298                return to;
1299        }
1300        
1301        public static Color scaleColor(Color color, float factor) {
1302                return new Color((int) (color.getRed() * factor),
1303                                                 (int) (color.getGreen() * factor),
1304                                                 (int) (color.getBlue() * factor),
1305                                                 (int) (color.getAlpha() * factor));
1306        }
1307        public static Color scaleColorOnly(Color color, float factor) {
1308                return new Color((int) (color.getRed() * factor),
1309                                                 (int) (color.getGreen() * factor),
1310                                                 (int) (color.getBlue() * factor),
1311                                                 (int) (color.getAlpha()));
1312        }
1313        
1314        public static Color scaleAlpha(Color color, float factor) {
1315                return new Color((int) (color.getRed() * 1f),
1316                                (int) (color.getGreen() * 1f),
1317                                (int) (color.getBlue() * 1f),
1318                                (int) (color.getAlpha() * factor));
1319        }
1320        
1321        public static Color setAlpha(Color color, int alpha) {
1322                if (alpha < 0) alpha = 0;
1323                if (alpha > 255) alpha = 255;
1324                return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
1325        }
1326        
1327        public static float getSizeNum(HullSize size) {
1328                if (size == null) {
1329                        return 1;
1330                }
1331                switch (size) {
1332                case CAPITAL_SHIP:
1333                        return 5;
1334                case CRUISER:
1335                        return 3;
1336                case DESTROYER:
1337                        return 2;
1338                case FIGHTER:
1339                case FRIGATE:
1340                case DEFAULT:
1341                        return 1;
1342                }
1343                return 1;
1344        }
1345        
1346        public static void unsetAll(String prefix, String memKey, MemoryAPI memory) {
1347                Map<String, MemoryAPI> memoryMap = new HashMap<String, MemoryAPI>();
1348                memoryMap.put(memKey, memory);
1349                new unsetAll().execute(null, null, Misc.tokenize(prefix), memoryMap);
1350        }
1351        
1352        
1353        
1354        public static float getTargetingRadius(Vector2f from, CombatEntityAPI target, boolean considerShield) {
1355                return Global.getSettings().getTargetingRadius(from, target, considerShield);
1356        }
1357        
1358        
1359        public static float getClosingSpeed(Vector2f p1, Vector2f p2, Vector2f v1, Vector2f v2) {               
1360                // direction from target to shooter
1361                Vector2f dir = Vector2f.sub(p1, p2, new Vector2f());
1362                normalise(dir);
1363                // velocity of target relative to shooter
1364                Vector2f relVel = Vector2f.sub(v2, v1, new Vector2f());
1365                float closingSpeed = Vector2f.dot(dir, relVel);
1366                return closingSpeed;
1367        }
1368        
1369        
1370        protected static DecimalFormat format = null;
1371        public static DecimalFormat getFormat() {
1372                if (format == null) {
1373                        DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.getDefault());
1374//                      symbols.setDecimalSeparator('.');
1375//                      symbols.setGroupingSeparator(','); 
1376                        format = new DecimalFormat("###,###,###,###,###", symbols);
1377                }
1378                return format;
1379        }
1380        
1381        /**
1382         * DGS = digit group separator, i.e.: 1000000 -> 1,000,000
1383         * @param num
1384         * @return
1385         */
1386        public static String getWithDGS(float num) {
1387                return getFormat().format(num);
1388        }
1389        
1390        /**
1391         * DGS = digit group separator, i.e.: 1000000 -> 1,000,000
1392         * @param num
1393         * @return
1394         */
1395        public static String getDGSCredits(float num) {
1396                return getFormat().format((int)num) + Strings.C;
1397        }
1398        
1399
1400        
1401        public static Vector2f getInterceptPointBasic(SectorEntityToken from, SectorEntityToken to) {
1402                float dist = getDistance(from.getLocation(), to.getLocation()) - from.getRadius() - to.getRadius();
1403                if (dist <= 0) return new Vector2f(to.getLocation());
1404                
1405                float closingSpeed = getClosingSpeed(from.getLocation(), to.getLocation(), from.getVelocity(), to.getVelocity());
1406                if (closingSpeed <= 10) return new Vector2f(to.getLocation());
1407                
1408                Vector2f toTarget = getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(from.getLocation(), to.getLocation()));
1409                Vector2f vel = new Vector2f(from.getVelocity());
1410                normalise(vel);
1411                float dot = Vector2f.dot(toTarget, vel);
1412                if (dot < 0) return new Vector2f(to.getLocation());
1413//              if (to.isPlayerFleet()) {
1414//                      System.out.println("23rwefe");
1415//              }
1416                float time = dist / closingSpeed;
1417                
1418                Vector2f point = new Vector2f(to.getVelocity());
1419                point.scale(time);
1420                Vector2f.add(point, to.getLocation(), point);
1421                return point;
1422        }
1423        
1424        
1425        
1426        /**
1427         * A flag can be set to true for several "reasons". As long as it hasn't been set
1428         * back to false for all of the "reasons", it will remain set to true.
1429         * 
1430         * For example, a fleet may be hostile because it's responding to comm relay interference,
1431         * and because the player is running with the transponder off. Until both are resolved,
1432         * the "hostile" flag will remain set to true.
1433         * 
1434         * Note: a flag can not be "set" to false. If it's set to false for all the current reasons,
1435         * the key is removed from memory. 
1436         * 
1437         * Returns whether the flag is still set after this method does its work.
1438         * @param memory
1439         * @param flagKey
1440         * @param reason
1441         * @param value
1442         * @return
1443         */
1444        public static boolean setFlagWithReason(MemoryAPI memory, String flagKey, String reason, boolean value, float expire) {
1445                String requiredKey = flagKey + "_" + reason;
1446
1447                if (value) {
1448                        memory.set(flagKey, true);
1449                        memory.set(requiredKey, value, expire);
1450                        memory.addRequired(flagKey, requiredKey);
1451                } else {
1452                        memory.unset(requiredKey);
1453                }
1454                
1455                return memory.contains(flagKey);
1456        }
1457        
1458        public static boolean flagHasReason(MemoryAPI memory, String flagKey, String reason) {
1459                String requiredKey = flagKey + "_" + reason;
1460                
1461                return memory.getBoolean(requiredKey);
1462        }
1463        
1464        public static void clearFlag(MemoryAPI memory, String flagKey) {
1465                for (String req : memory.getRequired(flagKey)) {
1466                        memory.unset(req);
1467                }
1468        }
1469        
1470        public static void makeLowRepImpact(CampaignFleetAPI fleet, String reason) {
1471                setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.MEMORY_KEY_LOW_REP_IMPACT, reason, true, -1);
1472        }
1473        public static void makeNoRepImpact(CampaignFleetAPI fleet, String reason) {
1474                setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.MEMORY_KEY_LOW_REP_IMPACT, reason, true, -1);
1475                setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.MEMORY_KEY_NO_REP_IMPACT, reason, true, -1);
1476        }
1477        
1478        public static void makeHostile(CampaignFleetAPI fleet) {
1479                fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_HOSTILE, true);
1480        }
1481        
1482        public static void makeHostileToPlayerTradeFleets(CampaignFleetAPI fleet) {
1483                fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_HOSTILE_TO_PLAYER_TRADE_FLEETS, true);
1484        }
1485        
1486        public static void makeHostileToAllTradeFleets(CampaignFleetAPI fleet) {
1487                fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_HOSTILE_TO_ALL_TRADE_FLEETS, true);
1488        }
1489        
1490        public static void makeNonHostileToFaction(CampaignFleetAPI fleet, String factionId, float dur) {
1491                makeNonHostileToFaction(fleet, factionId, true, dur);
1492        }
1493        public static void makeNonHostileToFaction(CampaignFleetAPI fleet, String factionId, boolean nonHostile, float dur) {
1494                String flag = MemFlags.MEMORY_KEY_MAKE_NON_HOSTILE + "_" + factionId;
1495                if (!nonHostile) {
1496                        fleet.getMemoryWithoutUpdate().unset(flag);
1497                } else {
1498                        fleet.getMemoryWithoutUpdate().set(flag, true, dur);
1499                }
1500        }
1501        public static void makeHostileToFaction(CampaignFleetAPI fleet, String factionId, float dur) {
1502                makeHostileToFaction(fleet, factionId, true, dur);
1503        }
1504        public static void makeHostileToFaction(CampaignFleetAPI fleet, String factionId, boolean hostile, float dur) {
1505                String flag = MemFlags.MEMORY_KEY_MAKE_HOSTILE + "_" + factionId;
1506                if (!hostile) {
1507                        fleet.getMemoryWithoutUpdate().unset(flag);
1508                } else {
1509                        fleet.getMemoryWithoutUpdate().set(flag, true, dur);
1510                }
1511        }
1512        
1513        public static boolean isFleetMadeHostileToFaction(CampaignFleetAPI fleet, FactionAPI faction) {
1514                return isFleetMadeHostileToFaction(fleet, faction.getId());
1515        }
1516        public static boolean isFleetMadeHostileToFaction(CampaignFleetAPI fleet, String factionId) {
1517                if (Factions.PLAYER.equals(factionId) && 
1518                                fleet.getMemoryWithoutUpdate().contains(MemFlags.MEMORY_KEY_MAKE_HOSTILE)) {
1519                        return true;
1520                }
1521                String flag = MemFlags.MEMORY_KEY_MAKE_HOSTILE + "_" + factionId;
1522                return fleet.getMemoryWithoutUpdate().getBoolean(flag);
1523        }
1524        
1525        public static void makeNotLowRepImpact(CampaignFleetAPI fleet, String reason) {
1526                setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.MEMORY_KEY_LOW_REP_IMPACT, reason, false, -1);
1527                setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.MEMORY_KEY_NO_REP_IMPACT, reason, false, -1);
1528        }
1529        
1530        
1531        public static String getAgoStringForTimestamp(long timestamp) {
1532                CampaignClockAPI clock = Global.getSector().getClock();
1533                float days = clock.getElapsedDaysSince(timestamp);
1534                
1535                if (days <= 1f) {
1536                        return "Today";
1537                } else if (days <= 6f) {
1538                        return (int)Math.ceil(days) + " days ago";
1539                } else if (days <= 7) {
1540                        return "1 week ago";
1541                } else if (days <= 14) {
1542                        return "2 weeks ago";
1543                } else if (days <= 21) {
1544                        return "3 weeks ago";
1545                } else if (days <= 30 + 14) {
1546                        return "1 month ago";
1547                } else if (days < 30 * 2 + 14) {
1548                        return "2 months ago";
1549                } else if (days < 30 * 3 + 14) {
1550                        return "3 months ago";
1551                } else {
1552                        return "Over 3 months ago";
1553                }
1554        }
1555        
1556        public static String getDetailedAgoString(long timestamp) {
1557                CampaignClockAPI clock = Global.getSector().getClock();
1558                int days = (int) clock.getElapsedDaysSince(timestamp);
1559                
1560                if (days == 0) {
1561                        return "0 days ago";
1562                } else if (days == 1) {
1563                        return "1 day ago";
1564                } else if (days <= 6) {
1565                        return (int)Math.ceil(days) + " days ago";
1566                } else if (days <= 7) {
1567                        return "1 week ago";
1568                } else if (days <= 14) {
1569                        return "2 weeks ago";
1570                } else if (days <= 21) {
1571                        return "3 weeks ago";
1572                } else {
1573                        int months = days / 30;
1574                        if (months <= 12) {
1575                                if (months <= 1) {
1576                                        return "1 month ago";
1577                                } else {
1578                                        return "" + months + " months ago";
1579                                }
1580                        } else {
1581                                int years = months / 12;
1582                                if (years <= 1) {
1583                                        return "1 cycle ago";
1584                                } else {
1585                                        return "" + years + " cycles ago";
1586                                }
1587                        }
1588                }
1589        }
1590        
1591        public static String getAtLeastStringForDays(int days) {
1592                if (days <= 1f) {
1593                        return "at least a day";
1594                } else if (days <= 6f) {
1595                        return "at least a few days";
1596                } else if (days <= 7 + 6) {
1597                        return "at least a week";
1598                } else if (days <= 14 + 6) {
1599                        return "at least two weeks";
1600                } else if (days <= 21 + 8) {
1601                        return "at least three weeks";
1602                } else if (days <= 30 + 29) {
1603                        return "at least a month";
1604                } else if (days < 30 * 2 + 29) {
1605                        return "at least two months";
1606                } else if (days < 30 * 3 + 29) {
1607                        return "at least three months";
1608                } else {
1609                        return "many months";
1610                }
1611        }
1612        
1613        public static String getStringForDays(int days) {
1614                if (days <= 1f) {
1615                        return "a day";
1616                } else if (days <= 6f) {
1617                        return "a few days";
1618                } else if (days <= 7 + 6) {
1619                        return "a week";
1620                } else if (days <= 14 + 6) {
1621                        return "two weeks";
1622                } else if (days <= 21 + 8) {
1623                        return "three weeks";
1624                } else if (days <= 30 + 29) {
1625                        return "a month";
1626                } else if (days < 30 * 2 + 29) {
1627                        return "two months";
1628                } else if (days < 30 * 3 + 29) {
1629                        return "three months";
1630                } else {
1631                        return "many months";
1632                }
1633        }
1634        
1635//      public static String getTimeStringForDays(int days) {
1636//              if (days <= 1f) {
1637//                      return "1 day";
1638//              } else if (days <= 6f) {
1639//                      return "at least a few days";
1640//              } else if (days <= 7) {
1641//                      return "at least a week";
1642//              } else if (days <= 14) {
1643//                      return "at least 2 weeks";
1644//              } else if (days <= 21) {
1645//                      return "at least 3 weeks";
1646//              } else if (days <= 30 + 14) {
1647//                      return "at least a month";
1648//              } else if (days < 30 * 2 + 14) {
1649//                      return "at least 2 months";
1650//              } else if (days < 30 * 3 + 14) {
1651//                      return "at least 3 months";
1652//              } else {
1653//                      return "many months";
1654//              }
1655//      }
1656        
1657        public static float getBurnLevelForSpeed(float speed) {
1658                speed -= Global.getSettings().getBaseTravelSpeed();
1659                if (speed < 0 || speed <= Global.getSettings().getFloat("minTravelSpeed") + 1f) speed = 0;
1660                float currBurn = speed / Global.getSettings().getSpeedPerBurnLevel();
1661                // 1/1/20: changed to not add +0.01f; not sure why it was there but could cause issues w/ isSlowMoving(), maybe?
1662                //return Math.round(currBurn + 0.01f);
1663                return Math.round(currBurn);
1664        }
1665        
1666        public static float getFractionalBurnLevelForSpeed(float speed) {
1667//              System.out.println("Speed: " + Global.getSector().getPlayerFleet().getVelocity().length());
1668//              System.out.println("Max: " + Global.getSector().getPlayerFleet().getTravelSpeed());
1669                speed -= Global.getSettings().getBaseTravelSpeed();
1670                if (speed < 0 || speed <= Global.getSettings().getFloat("minTravelSpeed") + 1f) speed = 0;
1671                float currBurn = speed / Global.getSettings().getSpeedPerBurnLevel();
1672                //System.out.println("ADFSDF: " +Math.round(currBurn));
1673                return currBurn;
1674        }
1675        
1676        public static float getSpeedForBurnLevel(float burnLevel) {
1677                float speed = Global.getSettings().getBaseTravelSpeed() + burnLevel * Global.getSettings().getSpeedPerBurnLevel();
1678                return speed;
1679        }
1680        
1681        public static float getFuelPerDay(CampaignFleetAPI fleet, float burnLevel) {
1682                float speed = Global.getSettings().getBaseTravelSpeed() + Global.getSettings().getSpeedPerBurnLevel() * burnLevel;
1683                return getFuelPerDayAtSpeed(fleet, speed);
1684        }
1685        
1686        public static float getFuelPerDayAtSpeed(CampaignFleetAPI fleet, float speed) {
1687                float perLY = fleet.getLogistics().getFuelCostPerLightYear();
1688
1689                // this is potentially evil - currently, the velocity is in units per SECOND, not per day
1690                speed = speed * Global.getSector().getClock().getSecondsPerDay();
1691                // now, speed is in units per day
1692                
1693                speed = speed / Global.getSettings().getUnitsPerLightYear();
1694                // ly/day now
1695                
1696                
1697                return speed * perLY;
1698        }
1699        
1700        public static float getLYPerDayAtBurn(CampaignFleetAPI fleet, float burnLevel) {
1701                float speed = Global.getSettings().getBaseTravelSpeed() + Global.getSettings().getSpeedPerBurnLevel() * burnLevel;
1702                return getLYPerDayAtSpeed(fleet, speed);
1703        }
1704        public static float getLYPerDayAtSpeed(CampaignFleetAPI fleet, float speed) {
1705                // this is potentially evil - currently, the velocity is in units per SECOND, not per day
1706                speed = speed * Global.getSector().getClock().getSecondsPerDay();
1707                // now, speed is in units per day
1708                speed = speed / Global.getSettings().getUnitsPerLightYear();
1709                // ly/day now
1710                
1711                return speed * 1f; // 1f days
1712        }
1713        
1714        private static Vector3f temp4 = new Vector3f();
1715        public static Color zeroColor = new Color(0,0,0,0);
1716        public static float getDistance(Vector3f v1, Vector3f v2)
1717        {
1718                return Vector3f.sub(v1, v2, temp4).length();
1719        }
1720        
1721        public static float getAngleDiff(float from, float to) {
1722                float diff = normalizeAngle(from - to);
1723                if (diff > 180) return 360 - diff;
1724                else return diff;
1725        }
1726        
1727        public static boolean isInArc(float direction, float arc, Vector2f from, Vector2f to) {
1728                direction = normalizeAngle(direction);
1729                if (arc >= 360) return true;
1730                if (direction < 0) direction = 360 + direction;
1731                Vector2f towardsTo = new Vector2f(to.x - from.x, to.y - from.y);
1732                if (towardsTo.lengthSquared() == 0) return false;
1733                float dir = Misc.getAngleInDegrees(towardsTo);
1734                if (dir < 0) dir = 360 + dir;
1735                float arcFrom = direction - arc/2f;
1736                if (arcFrom < 0) arcFrom = 360 + arcFrom;
1737                if (arcFrom > 360) arcFrom -= 360;
1738                float arcTo = direction + arc/2f;
1739                if (arcTo < 0) arcTo = 360 + arcTo;
1740                if (arcTo > 360) arcTo -= 360;
1741                
1742                if (dir >= arcFrom && dir <= arcTo) return true;
1743                if (dir >= arcFrom && arcFrom > arcTo) return true;
1744                if (dir <= arcTo && arcFrom > arcTo) return true;
1745                return false;
1746        }
1747        
1748        public static boolean isInArc(float direction, float arc, float test) {
1749                test = normalizeAngle(test);
1750                
1751                if (arc >= 360) return true;
1752                if (direction < 0) direction = 360 + direction;
1753                float dir = test;
1754                if (dir < 0) dir = 360 + dir;
1755                float arcFrom = direction - arc/2f;
1756                if (arcFrom < 0) arcFrom = 360 + arcFrom;
1757                if (arcFrom > 360) arcFrom -= 360;
1758                float arcTo = direction + arc/2f;
1759                if (arcTo < 0) arcTo = 360 + arcTo;
1760                if (arcTo > 360) arcTo -= 360;
1761                
1762                if (dir >= arcFrom && dir <= arcTo) return true;
1763                if (dir >= arcFrom && arcFrom > arcTo) return true;
1764                if (dir <= arcTo && arcFrom > arcTo) return true;
1765                return false;
1766        }
1767        
1768        
1769        public static SectorEntityToken addNebulaFromPNG(String image, float centerX, float centerY, LocationAPI location,
1770                        String category, String key, int tilesWide, int tilesHigh, StarAge age) {
1771                return addNebulaFromPNG(image, centerX, centerY, location, category, key, tilesWide, tilesHigh, Terrain.NEBULA, age);
1772        }
1773        
1774        
1775        public static SectorEntityToken addNebulaFromPNG(String image, float centerX, float centerY, LocationAPI location,
1776                                                                                String category, String key, int tilesWide, int tilesHigh,
1777                                                                                String terrainType, StarAge age) {
1778                try {
1779                        BufferedImage img = null;
1780                    //img = ImageIO.read(new File("../starfarer.res/res/data/campaign/terrain/nebula_test.png"));
1781                    img = ImageIO.read(Global.getSettings().openStream(image));
1782                    
1783                    int chunkSize = 10000;
1784                    int w = img.getWidth();
1785                    int h = img.getHeight();
1786                    Raster data = img.getData();
1787                    for (int i = 0; i < w; i += chunkSize) {
1788                        for (int j = 0; j < h; j += chunkSize) {
1789                                
1790                                int chunkWidth = chunkSize;
1791                                if (i + chunkSize > w) chunkWidth = w - i;
1792                                int chunkHeight = chunkSize;
1793                                if (j + chunkSize > h) chunkHeight = h - i;
1794                                
1795//                              boolean hasAny = false;
1796//                              for (int x = i; x < i + chunkWidth; x++) {
1797//                                      for (int y = j; y < j + chunkHeight; y++) {
1798//                                              int [] pixel = data.getPixel(i, h - j - 1, (int []) null);
1799//                                              int total = pixel[0] + pixel[1] + pixel[2];
1800//                                              if (total > 0) {
1801//                                                      hasAny = true;
1802//                                                      break;
1803//                                              }
1804//                                      }
1805//                              }
1806//                              if (!hasAny) continue;
1807                                
1808                                StringBuilder string = new StringBuilder();
1809                                for (int y = j + chunkHeight - 1; y >= j; y--) {
1810                                        for (int x = i; x < i + chunkWidth; x++) {
1811                                                int [] pixel = data.getPixel(x, h - y - 1, (int []) null);
1812                                                int total = pixel[0] + pixel[1] + pixel[2];
1813                                                if (total > 0) {
1814                                                        string.append("x");
1815                                                } else {
1816                                                        string.append(" ");
1817                                                }
1818                                        }
1819                                }
1820                                
1821                                float tileSize = NebulaTerrainPlugin.TILE_SIZE;
1822                                float x = centerX - tileSize * (float) w / 2f + (float) i * tileSize + chunkWidth / 2f * tileSize;
1823                                float y = centerY - tileSize * (float) h / 2f + (float) j * tileSize + chunkHeight / 2f * tileSize;
1824                                
1825                                SectorEntityToken curr = location.addTerrain(terrainType, new TileParams(string.toString(),
1826                                                                        chunkWidth, chunkHeight,
1827                                                                        category, key, tilesWide, tilesHigh, null));
1828                                curr.getLocation().set(x, y);
1829                                
1830                                if (location instanceof StarSystemAPI) {
1831                                                StarSystemAPI system = (StarSystemAPI) location;
1832                                                
1833                                                system.setAge(age);
1834                                                system.setHasSystemwideNebula(true);
1835                                }
1836                                
1837                                return curr;
1838                        }
1839                    }
1840                    return null;
1841                } catch (IOException e) {
1842                        throw new RuntimeException(e);
1843                }
1844        }
1845        
1846        
1847        public static void renderQuad(float x, float y, float width, float height, Color color, float alphaMult) {
1848                GL11.glColor4ub((byte)color.getRed(),
1849                                                (byte)color.getGreen(),
1850                                                (byte)color.getBlue(),
1851                                                (byte)((float)color.getAlpha() * alphaMult));
1852                
1853                GL11.glBegin(GL11.GL_QUADS);
1854                {
1855                        GL11.glVertex2f(x, y);
1856                        GL11.glVertex2f(x, y + height);
1857                        GL11.glVertex2f(x + width, y + height);
1858                        GL11.glVertex2f(x + width, y);
1859                }
1860                GL11.glEnd();
1861        }
1862        
1863        /**
1864         * Shortest distance from line to a point.
1865         * @param p1 line1
1866         * @param p2 line2
1867         * @param p3 point
1868         * @return
1869         */
1870        public static float distanceFromLineToPoint(Vector2f p1, Vector2f p2, Vector2f p3) {
1871                float u = (p3.x - p1.x) * (p2.x - p1.x) + (p3.y - p1.y) * (p2.y - p1.y);
1872                float denom = Vector2f.sub(p2, p1, new Vector2f()).length();
1873                denom *= denom;
1874                //if (denom == 0) return 0;
1875                u /= denom;
1876                Vector2f i = new Vector2f();
1877                i.x = p1.x + u * (p2.x - p1.x);
1878                i.y = p1.y + u * (p2.y - p1.y);
1879                return Vector2f.sub(i, p3, new Vector2f()).length();
1880        }
1881        
1882        public static Vector2f closestPointOnLineToPoint(Vector2f p1, Vector2f p2, Vector2f p3) {
1883                float u = (p3.x - p1.x) * (p2.x - p1.x) + (p3.y - p1.y) * (p2.y - p1.y);
1884                float denom = Vector2f.sub(p2, p1, new Vector2f()).length();
1885                denom *= denom;
1886                if (denom == 0) return p1;
1887                u /= denom;
1888                Vector2f i = new Vector2f();
1889                i.x = p1.x + u * (p2.x - p1.x);
1890                i.y = p1.y + u * (p2.y - p1.y);
1891                return i;
1892        }
1893        
1894        public static Vector2f closestPointOnSegmentToPoint(Vector2f p1, Vector2f p2, Vector2f p3) {
1895                float u = (p3.x - p1.x) * (p2.x - p1.x) + (p3.y - p1.y) * (p2.y - p1.y);
1896                float denom = Vector2f.sub(p2, p1, new Vector2f()).length();
1897                denom *= denom;
1898                
1899                u /= denom;
1900                
1901                // if closest point on line is outside the segment, clamp to on the segment
1902                if (u < 0) u = 0;
1903                if (u > 1) u = 1;
1904                
1905                Vector2f i = new Vector2f();
1906                i.x = p1.x + u * (p2.x - p1.x);
1907                i.y = p1.y + u * (p2.y - p1.y);
1908                return i;
1909        }
1910        
1911        public static boolean isPointInBounds(Vector2f p1, List<Vector2f> bounds) {
1912                Vector2f p2 = new Vector2f(p1);
1913                p2.x += 10000;
1914                int count = 0;
1915                for (int i = 0; i < 2; i++) {
1916                        for (int j = 0; j < bounds.size() - 1; j++) {
1917                                Vector2f s1 = bounds.get(j);
1918                                Vector2f s2 = bounds.get(j + 1);
1919                                Vector2f p = intersectSegments(p1, p2, s1, s2);
1920                                if (p != null) {
1921                                        if (Math.abs(p.x - s1.x) < 0.001f && 
1922                                                Math.abs(p.y - s1.y) < 0.001f) {
1923                                                continue; // JUST the first point, (p1, p2]
1924                                        }
1925                                        if (areSegmentsCoincident(p1, p2, s1, s2)) {
1926                                                continue;
1927                                        }
1928                                        count++;
1929                                }
1930                        }
1931                        if (i == 0 && count % 2 == 1) {
1932                                count = 0;
1933                                p2.y += 100;
1934                        } else {
1935                                break;
1936                        }
1937                }
1938                return count % 2 == 1;
1939        }
1940        
1941        public static Vector2f intersectSegments(Vector2f a1, Vector2f a2, Vector2f b1, Vector2f b2) {
1942                float denom = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
1943                float numUa = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
1944                float numUb = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
1945
1946                if (denom == 0 && !(numUa == 0 && numUb == 0)) { // parallel, not coincident
1947                        return null;
1948                }
1949
1950                if (denom == 0 && numUa == 0 && numUb == 0) { // coincident
1951                        float minX, minY, maxX, maxY;
1952                        if (a1.x < a2.x) {
1953                                minX = a1.x;
1954                                maxX = a2.x;
1955                        } else {
1956                                minX = a2.x;
1957                                maxX = a1.x;
1958                        }
1959                        if (a1.y < a2.y) {
1960                                minY = a1.y;
1961                                maxY = a2.y;
1962                        } else {
1963                                minY = a2.y;
1964                                maxY = a1.y;
1965                        }
1966                        // if either one of the endpoints in segment b is between the points in segment a,
1967                        // return that endpoint as the intersection.  Otherwise, no intersection.
1968                        if (b1.x >= minX && b1.x <= maxX && b1.y >= minY && b1.y <= maxY) {
1969                                return new Vector2f(b1);
1970                        } else if (b2.x >= minX && b2.x <= maxX && b2.y >= minY && b2.y <= maxY) {
1971                                return new Vector2f(b2);
1972                        } else {
1973                                return null;
1974                        }
1975                }
1976
1977                float Ua = numUa / denom;
1978                float Ub = numUb / denom;
1979                if (Ua >=0 && Ua <= 1 && Ub >= 0 && Ub <= 1) { // segments intersect
1980                        Vector2f result = new Vector2f();
1981//                      if (Ua <= 0.001f) {
1982//                              result.x = a1.x;
1983//                              result.y = a1.y;
1984//                      } else if (Ua >= 0.999f) {
1985//                              result.x = a2.x;
1986//                              result.y = a2.y;
1987//                      } else {
1988                                result.x = a1.x + Ua * (a2.x - a1.x);
1989                                result.y = a1.y + Ua * (a2.y - a1.y);
1990//                      }
1991                        return result;
1992                } else { // lines intersect, but segments do not
1993                        return null;
1994                }
1995
1996        }
1997        
1998        
1999        public static Vector2f intersectLines(Vector2f a1, Vector2f a2, Vector2f b1, Vector2f b2) {
2000                float denom = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
2001                float numUa = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
2002                float numUb = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
2003
2004                if (denom == 0 && !(numUa == 0 && numUb == 0)) { // parallel, not coincident
2005                        return null;
2006                }
2007
2008                if (denom == 0 && numUa == 0 && numUb == 0) { // coincident
2009                        return new Vector2f(a1);
2010                }
2011
2012                float Ua = numUa / denom;
2013                float Ub = numUb / denom;
2014                Vector2f result = new Vector2f();
2015                result.x = a1.x + Ua * (a2.x - a1.x);
2016                result.y = a1.y + Ua * (a2.y - a1.y);
2017                return result;
2018        }
2019        
2020        
2021        
2022        
2023        /**
2024         * Going from p1 to p2.  Returns the closer intersection.
2025         * @param p1
2026         * @param p2
2027         * @param p3
2028         * @param r
2029         * @return
2030         */
2031        public static Vector2f intersectSegmentAndCircle(Vector2f p1, Vector2f p2, Vector2f p3, float r) {
2032
2033                float uNom = (p3.x - p1.x) * (p2.x - p1.x) + (p3.y - p1.y) * (p2.y - p1.y);
2034                float uDenom = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
2035
2036                Vector2f closest = new Vector2f();
2037                if (uDenom == 0) { // p1 and p2 are coincident
2038                        closest.set(p1);
2039                } else {
2040                        float u = uNom / uDenom;
2041                        closest.x = p1.x + u * (p2.x - p1.x);
2042                        closest.y = p1.y + u * (p2.y - p1.y);
2043                }
2044
2045                float distSq = (closest.x - p3.x) * (closest.x - p3.x) + (closest.y - p3.y) * (closest.y - p3.y);
2046                if (distSq > r * r) { // closest point is farther than radius
2047                        //System.out.println("shorted");
2048                        return null;
2049                } else if (uDenom == 0) {
2050                        return closest; // in the case where p1==p2 and they're inside the circle, return p1.
2051                }
2052
2053                float a = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
2054                float b = 2f * ( (p2.x - p1.x) * (p1.x - p3.x) + (p2.y - p1.y) *
2055                                (p1.y - p3.y) );
2056                float c = p3.x * p3.x + p3.y * p3.y + p1.x * p1.x + p1.y * p1.y - 2f
2057                                * (p3.x * p1.x + p3.y * p1.y) - r * r;
2058
2059                float bb4ac = b * b - 4f * a * c;
2060
2061                if (bb4ac < 0) return null;
2062
2063                float mu1 = (-b + (float) Math.sqrt(bb4ac)) / (2 * a);
2064                float mu2 = (-b - (float) Math.sqrt(bb4ac)) / (2 * a);
2065
2066                float minMu = mu1;
2067                if ((mu2 < minMu && mu2 >= 0) || minMu < 0) minMu = mu2;
2068
2069                if (minMu < 0 || minMu > 1) {
2070                        float p2DistSq = (p2.x - p3.x) * (p2.x - p3.x) + (p2.y - p3.y) * (p2.y - p3.y);
2071                        if (p2DistSq <= r * r) return p2;
2072                        else return null;
2073                }
2074                //System.out.println("mu1: " + mu1 + ", mu2: " + mu2);
2075
2076                Vector2f result = new Vector2f();
2077                result.x = p1.x + minMu * (p2.x - p1.x);
2078                result.y = p1.y + minMu * (p2.y - p1.y);
2079
2080                return result;
2081        }
2082        
2083        
2084        public static boolean areSegmentsCoincident(Vector2f a1, Vector2f a2, Vector2f b1, Vector2f b2) {
2085                float denom = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
2086                float numUa = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
2087                float numUb = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) *(a1.x - b1.x);
2088
2089                if (denom == 0 && !(numUa == 0 && numUb == 0)) { // parallel, not coincident
2090                        return false;
2091                }
2092
2093                if (denom == 0 && numUa == 0 && numUb == 0) { // coincident
2094                        return true;
2095                }
2096                
2097                return false;
2098        }
2099        
2100        public static Vector2f getPerp(Vector2f v) {
2101                Vector2f perp = new Vector2f();
2102                perp.x = v.y;
2103                perp.y = -v.x;
2104                return perp;
2105        }
2106        
2107        public static float getClosestTurnDirection(float facing, float desired) {
2108                float diff = Misc.normalizeAngle(desired) - Misc.normalizeAngle(facing);
2109                if (diff < 0) diff += 360;
2110                
2111                if (diff == 0 || diff == 360f) {
2112                        return 0f;
2113                } else if (diff > 180) {
2114                        return -1f;
2115                } else {
2116                        return 1f;
2117                }
2118//              facing = normalizeAngle(facing);
2119//              desired = normalizeAngle(desired);
2120//              if (facing == desired) return 0;
2121//              
2122//              Vector2f desiredVec = getUnitVectorAtDegreeAngle(desired);
2123//              //if (desiredVec.lengthSquared() == 0) return 0;
2124//              Vector2f more = getUnitVectorAtDegreeAngle(facing + 1);
2125//              Vector2f less = getUnitVectorAtDegreeAngle(facing - 1);
2126//              
2127//              float fromMore = Vector2f.angle(more, desiredVec);
2128//              float fromLess = Vector2f.angle(less, desiredVec);
2129//              if (fromMore > fromLess) return -1f;
2130//              return 1f;
2131        }
2132        
2133        public static float getClosestTurnDirection(float facing, Vector2f from, Vector2f to) {
2134                float diff = Misc.normalizeAngle(getAngleInDegrees(from, to)) - Misc.normalizeAngle(facing);
2135                if (diff < 0) diff += 360;
2136                
2137                if (diff == 0 || diff == 360f) {
2138                        return 0f;
2139                } else if (diff > 180) {
2140                        return -1f;
2141                } else {
2142                        return 1f;
2143                }
2144//              Vector2f desired = getDiff(to, from);
2145//              if (desired.lengthSquared() == 0) return 0;
2146//              //float angle = getAngleInDegrees(desired);
2147//              Vector2f more = getUnitVectorAtDegreeAngle(facing + 1);
2148//              Vector2f less = getUnitVectorAtDegreeAngle(facing - 1);
2149//              
2150//              float fromMore = Vector2f.angle(more, desired);
2151//              float fromLess = Vector2f.angle(less, desired);
2152//              if (fromMore == fromLess) return 0f;
2153//              if (fromMore > fromLess) return -1f;
2154//              return 1f;
2155        }
2156        
2157        public static float getClosestTurnDirection(Vector2f one, Vector2f two) {
2158                return getClosestTurnDirection(getAngleInDegrees(one), new Vector2f(0, 0), two);
2159        }
2160        
2161        public static Vector2f getDiff(Vector2f v1, Vector2f v2)
2162        {
2163                //Vector2f result = new Vector2f();
2164                return Vector2f.sub(v1, v2, new Vector2f());
2165        }
2166        
2167        public static MarketAPI getSourceMarket(CampaignFleetAPI fleet) {
2168                String id = fleet.getMemoryWithoutUpdate().getString(MemFlags.MEMORY_KEY_SOURCE_MARKET);
2169                if (id == null) return null;
2170                MarketAPI market = Global.getSector().getEconomy().getMarket(id);
2171                return market;
2172        }
2173        
2174        public static SectorEntityToken getSourceEntity(CampaignFleetAPI fleet) {
2175                String id = fleet.getMemoryWithoutUpdate().getString(MemFlags.MEMORY_KEY_SOURCE_MARKET);
2176                if (id == null) return null;
2177                MarketAPI market = Global.getSector().getEconomy().getMarket(id);
2178                if (market != null && market.getPrimaryEntity() != null) {
2179                        return market.getPrimaryEntity();
2180                }
2181                SectorEntityToken entity = Global.getSector().getEntityById(id);
2182                return entity;
2183        }
2184        
2185        public static float getSpawnChanceMult(Vector2f locInHyper) {
2186                if (Global.getSector().getPlayerFleet() == null) return 1f;
2187                
2188                float min = Global.getSettings().getFloat("minFleetSpawnChanceMult");
2189                float range = Global.getSettings().getFloat("minFleetSpawnChanceRangeLY");
2190                
2191                Vector2f playerLoc = Global.getSector().getPlayerFleet().getLocationInHyperspace();
2192                float distLY = getDistanceLY(playerLoc, locInHyper);
2193                
2194                float f = (1f - Math.min(1f, distLY/range));
2195                return min + (1f - min) * f * f;
2196        }
2197        
2198        public static Vector2f pickHyperLocationNotNearPlayer(Vector2f from, float minDist) {
2199                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
2200                float r = 2000f;
2201                if (player == null || !player.isInHyperspace()) {
2202                        return getPointWithinRadius(from, r);
2203                }
2204                float dist = Misc.getDistance(player.getLocation(), from);
2205                if (dist > minDist + r) {
2206                        return getPointWithinRadius(from, r);
2207                }
2208                float dir = Misc.getAngleInDegrees(player.getLocation(), from);
2209                Vector2f v = Misc.getUnitVectorAtDegreeAngle(dir);
2210                v.scale(minDist + 2000 - dist);
2211                Vector2f.add(v, from, v);
2212                return getPointWithinRadius(v, r);
2213        }
2214        
2215        public static Vector2f pickLocationNotNearPlayer(LocationAPI where, Vector2f from, float minDist) {
2216                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
2217                float r = 2000f;
2218                if (player == null || player.getContainingLocation() != where) {
2219                        return getPointWithinRadius(from, r);
2220                }
2221                float dist = Misc.getDistance(player.getLocation(), from);
2222                if (dist > minDist + r) {
2223                        return getPointWithinRadius(from, r);
2224                }
2225                float dir = Misc.getAngleInDegrees(player.getLocation(), from);
2226                Vector2f v = Misc.getUnitVectorAtDegreeAngle(dir);
2227                v.scale(minDist + 2000 - dist);
2228                Vector2f.add(v, from, v);
2229                return getPointWithinRadius(v, r);
2230        }
2231        
2232        public static float getBattleJoinRange() {
2233                return Global.getSettings().getFloat("battleJoinRange");
2234        }
2235        
2236        public static void wiggle(Vector2f v, float max) {
2237                v.x += (max * 2f * ((float) Math.random() - 0.5f));
2238                v.y += (max * 2f * ((float) Math.random() - 0.5f));
2239                if (v.length() == 0 || v.lengthSquared() == 0) {
2240                        v.x += max * 0.25f;
2241                }
2242        }
2243        
2244        
2245        public static boolean isPlayerOrCombinedPlayerPrimary(CampaignFleetAPI fleet) {
2246                if (fleet.isPlayerFleet()) return true;
2247                
2248                if (fleet.getBattle() != null && fleet.getBattle().isOnPlayerSide(fleet) &&
2249                                fleet.getBattle().isPlayerPrimary()) {
2250                                        return true;
2251                }
2252                return false;
2253        }
2254        
2255        public static boolean isPlayerOrCombinedContainingPlayer(CampaignFleetAPI fleet) {
2256                if (fleet.isPlayerFleet()) return true;
2257                
2258                if (fleet.getBattle() != null && fleet.getBattle().isOnPlayerSide(fleet)) {
2259                        return true;
2260                }
2261                return false;
2262        }
2263
2264        public static final String ASTEROID_SOURCE = "misc_astrdSource";
2265        
2266        public static AsteroidSource getAsteroidSource(SectorEntityToken asteroid) {
2267                if (asteroid.getCustomData().containsKey(ASTEROID_SOURCE)) {
2268                        return (AsteroidSource) asteroid.getCustomData().get(ASTEROID_SOURCE);
2269                }
2270                return null;
2271        }
2272        public static void setAsteroidSource(SectorEntityToken asteroid, AsteroidSource source) {
2273                asteroid.getCustomData().put(ASTEROID_SOURCE, source);
2274        }
2275        public static void clearAsteroidSource(SectorEntityToken asteroid) {
2276                asteroid.getCustomData().remove(ASTEROID_SOURCE);
2277        }
2278        
2279        public static boolean isFastStart() {
2280                return Global.getSector().getMemoryWithoutUpdate().getBoolean("$fastStart");
2281        }
2282        public static boolean isFastStartExplorer() {
2283                return Global.getSector().getMemoryWithoutUpdate().getBoolean("$fastStartExplorer");
2284        }
2285        public static boolean isFastStartMerc() {
2286                return Global.getSector().getMemoryWithoutUpdate().getBoolean("$fastStartMerc");
2287        }
2288        
2289        public static boolean isEasy() {
2290                return Difficulties.EASY.equals(Global.getSector().getDifficulty());
2291        }
2292        
2293        public static boolean isNormal() {
2294                return Difficulties.NORMAL.equals(Global.getSector().getDifficulty());
2295        }
2296        
2297        
2298//      public static void main(String[] args) {
2299//              System.out.println("TEST");
2300//              String s = "te\\\"st==$global.one $player.a $>=$sdfdsf\"++++dfdsf0---\"\"quoted \\\"=+!<>param\" one two 1234 1  2342 24  \t\t234234";
2301//              //String s = "menuState sdfsdf sdfsdf \"\"= \"main \\\" 1234\"";
2302//              //s = "!sdfsdAKJKFHD";
2303//              List<Token> result = tokenize(s);
2304//              for (Token str : result) {
2305//                      System.out.println(str);
2306//              }
2307                //addNebulaFromPNG();
2308//              String s = "ffff0000";
2309//              System.out.println(Long.parseLong(s, 16));
2310                //System.out.println(Long.parseLong("aa0F245C", 16));
2311//      }
2312
2313        public static CampaignTerrainAPI getHyperspaceTerrain() {
2314                for (CampaignTerrainAPI curr : Global.getSector().getHyperspace().getTerrainCopy()) {
2315                        if (curr.getPlugin() instanceof HyperspaceTerrainPlugin) {
2316                                return curr;
2317                        }
2318                }
2319                return null;
2320        }
2321        public static HyperspaceTerrainPlugin getHyperspaceTerrainPlugin() {
2322                CampaignTerrainAPI hyper = getHyperspaceTerrain();
2323                if (hyper != null) {
2324                        return (HyperspaceTerrainPlugin) hyper.getPlugin();
2325                }
2326                return null;
2327        }
2328        
2329        public static boolean isInAbyss(Vector2f loc) {
2330                return getAbyssalDepth(loc) > 0;
2331        }
2332        public static boolean isInAbyss(SectorEntityToken entity) {
2333                return getAbyssalDepth(entity) > 0;
2334        }
2335        public static float getAbyssalDepth(Vector2f loc) {
2336                return getAbyssalDepth(loc, false);
2337        }
2338        public static float getAbyssalDepth(Vector2f loc, boolean uncapped) {
2339                HyperspaceTerrainPlugin plugin = getHyperspaceTerrainPlugin();
2340                if (plugin == null) return 0f;
2341                return plugin.getAbyssalDepth(loc, uncapped);
2342        }
2343        
2344        public static List<StarSystemAPI> getAbyssalSystems() {
2345                HyperspaceTerrainPlugin plugin = getHyperspaceTerrainPlugin();
2346                if (plugin == null) return new ArrayList<StarSystemAPI>();
2347                return plugin.getAbyssalSystems();
2348        }
2349        
2350        public static float getAbyssalDepthOfPlayer() {
2351                return getAbyssalDepth(Global.getSector().getPlayerFleet());
2352        }
2353        public static float getAbyssalDepthOfPlayer(boolean uncapped) {
2354                return getAbyssalDepth(Global.getSector().getPlayerFleet(), uncapped);
2355        }
2356        public static float getAbyssalDepth(SectorEntityToken entity) {
2357                return getAbyssalDepth(entity, false);
2358        }
2359        public static float getAbyssalDepth(SectorEntityToken entity, boolean uncapped) {
2360                if (entity == null || !entity.isInHyperspace()) return 0f;
2361                return getAbyssalDepth(entity.getLocation());
2362        }
2363        
2364        public static boolean isInsideBlackHole(CampaignFleetAPI fleet, boolean includeEventHorizon) {
2365                for (PlanetAPI planet : fleet.getContainingLocation().getPlanets()) {
2366                        if (planet.isStar() && planet.getSpec() != null && planet.getSpec().isBlackHole()) {
2367                                float dist = Misc.getDistance(fleet, planet);
2368                                if (dist < planet.getRadius() + fleet.getRadius()) {
2369                                        return true;
2370                                } else if (includeEventHorizon) {
2371                                        StarCoronaTerrainPlugin corona = getCoronaFor(planet);
2372                                        if (corona != null && corona.containsEntity(fleet)) {
2373                                                return true;
2374                                        }
2375                                }
2376                        }
2377                }
2378                return false;
2379        }
2380        
2381        
2382        public static StarCoronaTerrainPlugin getCoronaFor(PlanetAPI star) {
2383                if (star == null) return null;
2384                
2385                for (CampaignTerrainAPI curr : star.getContainingLocation().getTerrainCopy()) {
2386                        if (curr.getPlugin() instanceof StarCoronaTerrainPlugin) {
2387                                StarCoronaTerrainPlugin corona = (StarCoronaTerrainPlugin) curr.getPlugin();
2388                                if (corona.getRelatedEntity() == star) return corona;
2389                        }
2390                }
2391                return null;
2392        }
2393        
2394        public static MagneticFieldTerrainPlugin getMagneticFieldFor(PlanetAPI planet) {
2395                if (planet == null || planet.getContainingLocation() == null) return null;
2396                
2397                for (CampaignTerrainAPI curr : planet.getContainingLocation().getTerrainCopy()) {
2398                        if (curr.getPlugin() instanceof MagneticFieldTerrainPlugin) {
2399                                MagneticFieldTerrainPlugin field = (MagneticFieldTerrainPlugin) curr.getPlugin();
2400                                if (field.getRelatedEntity() == planet) return field;
2401                        }
2402                }
2403                return null;
2404        }
2405        
2406        public static PulsarBeamTerrainPlugin getPulsarFor(PlanetAPI star) {
2407                for (CampaignTerrainAPI curr : star.getContainingLocation().getTerrainCopy()) {
2408                        if (curr.getPlugin() instanceof PulsarBeamTerrainPlugin) {
2409                                PulsarBeamTerrainPlugin corona = (PulsarBeamTerrainPlugin) curr.getPlugin();
2410                                if (corona.getRelatedEntity() == star) return corona;
2411                        }
2412                }
2413                return null;
2414        }
2415        
2416        public static boolean hasPulsar(StarSystemAPI system) {
2417                return system != null && system.hasPulsar();
2418//              if (system.getStar() != null && system.getStar().getSpec().isPulsar()) return true;
2419//              if (system.getSecondary() != null && system.getSecondary().getSpec().isPulsar()) return true;
2420//              if (system.getTertiary() != null && system.getTertiary().getSpec().isPulsar()) return true;
2421//              return false;
2422        }
2423        
2424        public static String getCommissionFactionId() {
2425                String str = Global.getSector().getCharacterData().getMemoryWithoutUpdate().getString(MemFlags.FCM_FACTION);
2426                return str;
2427        }
2428        
2429        public static FactionAPI getCommissionFaction() {
2430                String id = getCommissionFactionId();
2431                if (id != null) {
2432                        return Global.getSector().getFaction(id);
2433                }
2434                return null;
2435        }
2436        
2437        public static FactionCommissionIntel getCommissionIntel() {
2438                Object obj = Global.getSector().getCharacterData().getMemoryWithoutUpdate().get(MemFlags.FCM_EVENT);
2439                if (obj instanceof FactionCommissionIntel) {
2440                        return (FactionCommissionIntel) obj;
2441                }
2442                return null;
2443        }
2444        
2445        
2446        public static boolean caresAboutPlayerTransponder(CampaignFleetAPI fleet) {
2447//              if (fleet.isInCurrentLocation()) {
2448//                      System.out.println("efwefew");
2449//              }
2450                if (fleet.getFaction().isPlayerFaction()) return false;
2451                
2452                boolean caresAboutTransponder = true;
2453                if (fleet.getFaction().getCustomBoolean(Factions.CUSTOM_ALLOWS_TRANSPONDER_OFF_TRADE)) {
2454                        caresAboutTransponder = false;
2455                }
2456                MarketAPI source = Misc.getSourceMarket(fleet);
2457                if (source != null && source.hasCondition(Conditions.FREE_PORT)) {
2458                        caresAboutTransponder = false;
2459                }
2460                
2461                if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PATROL_ALLOW_TOFF)) {
2462                        caresAboutTransponder = false;
2463                }
2464                
2465                // prevents "infinitely chase player, re-interact, and don't demand anything or act hostile" scenario
2466                if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_MAKE_NON_HOSTILE)) {
2467                        caresAboutTransponder = false;
2468                }
2469                
2470                if (caresAboutTransponder && source != null && source.getPrimaryEntity() != null) {
2471                        final CampaignFleetAPI player = Global.getSector().getPlayerFleet();
2472                        if (player == null || player.isInHyperspace()) {
2473                                caresAboutTransponder = false;
2474                        } else {
2475                                caresAboutTransponder = source.getPrimaryEntity().getContainingLocation() == player.getContainingLocation();
2476//                              boolean alreadyTargetingPlayer = false;
2477//                              if (fleet.getAI() instanceof ModularFleetAIAPI) {
2478//                                      ModularFleetAIAPI ai = (ModularFleetAIAPI) fleet.getAI();
2479//                                      SectorEntityToken target = ai.getTacticalModule().getTarget();
2480//                                      alreadyTargetingPlayer = target == player;
2481//                              }
2482//                              if (!alreadyTargetingPlayer) {
2483////                                    float max = Global.getSettings().getFloat("maxTransponderRequiredRangeAroundMarket");
2484////                                    float dist = getDistance(player.getLocation(), source.getPrimaryEntity().getLocation());
2485//                                      float max = Global.getSettings().getFloat("maxTransponderRequiredRangeAroundMarketSystem");
2486//                                      float dist = getDistanceLY(player.getLocationInHyperspace(), source.getLocationInHyperspace());
2487//                                      if (dist > max) {
2488//                                              caresAboutTransponder = false;
2489//                                      }
2490//                              }
2491                        }
2492                }
2493                
2494                return caresAboutTransponder;
2495        }
2496        
2497
2498        public static interface FindShipFilter {
2499                public boolean matches(ShipAPI ship);
2500        }
2501        
2502        public static ShipAPI findClosestShipEnemyOf(ShipAPI ship, Vector2f locFromForSorting, HullSize smallestToNote, float maxRange, boolean considerShipRadius) {
2503                return findClosestShipEnemyOf(ship, locFromForSorting, smallestToNote, maxRange, considerShipRadius, null);
2504        }
2505        public static ShipAPI findClosestShipEnemyOf(ShipAPI ship, Vector2f locFromForSorting, HullSize smallestToNote, float maxRange, boolean considerShipRadius, FindShipFilter filter) {
2506                CombatEngineAPI engine = Global.getCombatEngine();
2507                List<ShipAPI> ships = engine.getShips();
2508                float minDist = Float.MAX_VALUE;
2509                ShipAPI closest = null;
2510                for (ShipAPI other : ships) {
2511                        if (other.getHullSize().ordinal() < smallestToNote.ordinal()) continue;
2512                        if (other.isShuttlePod()) continue;
2513                        if (other.isHulk()) continue;
2514                        if (ship.getOwner() != other.getOwner() && other.getOwner() != 100) {
2515                                if (filter != null && !filter.matches(other)) continue;
2516                                
2517                                float dist = getDistance(ship.getLocation(), other.getLocation());
2518                                float distSort = getDistance(locFromForSorting, other.getLocation());
2519                                float radSum = ship.getCollisionRadius() + other.getCollisionRadius();
2520                                if (!considerShipRadius) radSum = 0;
2521                                if (dist > maxRange + radSum) continue;
2522                                if (distSort < minDist) {
2523                                        closest = other;
2524                                        minDist = distSort;
2525                                }
2526                        }
2527                }
2528                return closest;
2529        }
2530        
2531        public static ShipAPI findClosestShipTo(ShipAPI ship, Vector2f locFromForSorting, HullSize smallestToNote, float maxRange, boolean considerShipRadius, boolean allowHulks, FindShipFilter filter) {
2532                CombatEngineAPI engine = Global.getCombatEngine();
2533                List<ShipAPI> ships = engine.getShips();
2534                float minDist = Float.MAX_VALUE;
2535                ShipAPI closest = null;
2536                for (ShipAPI other : ships) {
2537                        if (other == ship) continue;
2538                        if (other.getHullSize().ordinal() < smallestToNote.ordinal()) continue;
2539                        if (other.isShuttlePod()) continue;
2540                        if (other.isHulk()) continue;
2541                        if (allowHulks || other.getOwner() != 100) {
2542                                if (filter != null && !filter.matches(other)) continue;
2543                                
2544                                float dist = getDistance(ship.getLocation(), other.getLocation());
2545                                float distSort = getDistance(locFromForSorting, other.getLocation());
2546                                float radSum = ship.getCollisionRadius() + other.getCollisionRadius();
2547                                if (!considerShipRadius) radSum = 0;
2548                                if (dist > maxRange + radSum) continue;
2549                                if (distSort < minDist) {
2550                                        closest = other;
2551                                        minDist = distSort;
2552                                }
2553                        }
2554                }
2555                return closest;
2556        }       
2557        
2558        
2559        public static <T extends Enum<T>> T mapToEnum(JSONObject json, String key, Class<T> enumType, T defaultOption) throws JSONException {
2560                return mapToEnum(json, key, enumType, defaultOption, true);
2561        }
2562        public static <T extends Enum<T>> T mapToEnum(JSONObject json, String key, Class<T> enumType, T defaultOption, boolean required) throws JSONException {
2563                String val = json.optString(key);
2564                if (val == null || val.equals("")) {
2565                        if (defaultOption == null && required) {
2566                                throw new RuntimeException("Key [" + key + "] is required");
2567                        }
2568                        return defaultOption;
2569                }
2570                try {
2571                        return (T) Enum.valueOf(enumType, val);
2572                } catch (IllegalArgumentException e) {
2573                        throw new RuntimeException("Key [" + key + "] has invalid value [" + val + "] in [" + json.toString() + "]");
2574                }
2575        }
2576
2577        public static Color getColor(JSONObject json, String key) throws JSONException {
2578                if (!json.has(key)) return Color.white;
2579                JSONArray arr = json.getJSONArray(key);
2580                return new Color(arr.getInt(0), arr.getInt(1), arr.getInt(2), arr.getInt(3));
2581        }
2582        
2583        public static Color optColor(JSONObject json, String key, Color defaultValue) throws JSONException {
2584                if (!json.has(key)) return defaultValue;
2585                JSONArray arr = json.getJSONArray(key);
2586                return new Color(arr.getInt(0), arr.getInt(1), arr.getInt(2), arr.getInt(3));
2587        }
2588        
2589        public static Vector2f getVector(JSONObject json, String arrayKey, Vector2f def) throws JSONException {
2590                if (!json.has(arrayKey)) return def;
2591                return getVector(json, arrayKey);
2592        }
2593        public static Vector2f getVector(JSONObject json, String arrayKey) throws JSONException {
2594                Vector2f v = new Vector2f();
2595                JSONArray arr = json.getJSONArray(arrayKey);
2596                v.set((float) arr.getDouble(0), (float) arr.getDouble(1));
2597                return v;
2598        }
2599        
2600        public static Vector3f getVector3f(JSONObject json, String arrayKey) throws JSONException {
2601                Vector3f v = new Vector3f();
2602                JSONArray arr = json.getJSONArray(arrayKey);
2603                v.set((float) arr.getDouble(0), (float) arr.getDouble(1), (float) arr.getDouble(1));
2604                return v;
2605        }
2606        
2607        public static Vector2f optVector(JSONObject json, String arrayKey) {
2608                Vector2f v = new Vector2f();
2609                JSONArray arr = json.optJSONArray(arrayKey);
2610                if (arr == null) return null;
2611                v.set((float) arr.optDouble(0), (float) arr.optDouble(1));
2612                return v;
2613        }
2614        
2615        public static Vector3f optVector3f(JSONObject json, String arrayKey) throws JSONException {
2616                Vector3f v = new Vector3f();
2617                JSONArray arr = json.optJSONArray(arrayKey);
2618                if (arr == null) return new Vector3f();
2619                v.set((float) arr.getDouble(0), (float) arr.getDouble(1), (float) arr.getDouble(2));
2620                return v;
2621        }       
2622        
2623        public static Vector2f getVector(JSONObject json, String arrayKey, int index) throws JSONException {
2624                Vector2f v = new Vector2f();
2625                JSONArray arr = json.getJSONArray(arrayKey);
2626                v.set((float) arr.getDouble(index * 2 + 0), (float) arr.getDouble(index * 2 + 1));
2627                return v;
2628        }       
2629        
2630        public static void normalizeNoise(float[][] noise) {
2631                float minNoise = 1;
2632                float maxNoise = 0;
2633                for (int i = 0; i < noise.length; i++) {
2634                        for (int j = 0; j < noise[0].length; j++) {
2635                                if (noise[i][j] != -1) {
2636                                        if (noise[i][j] > maxNoise)
2637                                                maxNoise = noise[i][j];
2638                                        if (noise[i][j] < minNoise)
2639                                                minNoise = noise[i][j];
2640                                }
2641                        }
2642                }
2643                
2644                if (minNoise >= maxNoise) return;
2645
2646                float range = maxNoise - minNoise;
2647
2648                for (int i = 0; i < noise.length; i++) {
2649                        for (int j = 0; j < noise[0].length; j++) {
2650                                if (noise[i][j] != -1) {
2651                                        float newNoise = (noise[i][j] - minNoise) / range;
2652                                        noise[i][j] = newNoise;
2653                                } else {
2654                                        if (i > 0)
2655                                                noise[i][j] = noise[i - 1][j];
2656                                        else if (i < noise.length - 1)
2657                                                noise[i][j] = noise[i + 1][j];
2658                                        else
2659                                                noise[i][j] = .5f;
2660                                }
2661                        }
2662                }
2663        }
2664
2665        public static float [][] initNoise(Random random, int w, int h, float spikes) {
2666                if (random == null) random = Misc.random;
2667                float [][] noise = new float [w][h];
2668                for (int i = 0; i < noise.length; i++) {
2669                        for (int j = 0; j < noise[0].length; j++) {
2670                                noise[i][j] = -1f;
2671                        }
2672                }
2673                noise[0][0] = random.nextFloat() * spikes;
2674                noise[0][noise[0].length - 1] = random.nextFloat() * spikes;
2675                noise[noise.length - 1][0] = random.nextFloat() * spikes;
2676                noise[noise.length - 1][noise[0].length - 1] = random.nextFloat() * spikes;
2677                return noise;
2678        }
2679        
2680        public static void genFractalNoise(Random random, float[][] noise, int x1, int y1,
2681                                                                                int x2, int y2, int iter, float spikes) {
2682                if (x1 + 1 >= x2 || y1 + 1 >= y2) return; // no more values to fill
2683
2684                int midX = (x1 + x2) / 2;
2685                int midY = (y1 + y2) / 2;
2686
2687                fill(random, noise, midX, y1, x1, y1, x2, y1, iter, spikes);
2688                fill(random, noise, midX, y2, x1, y2, x2, y2, iter, spikes);
2689                fill(random, noise, x1, midY, x1, y1, x1, y2, iter, spikes);
2690                fill(random, noise, x2, midY, x2, y1, x2, y2, iter, spikes);
2691                
2692                // averaging 4 neighboring values
2693                fill(random, noise, midX, midY, midX, y1, midX, y2, iter, spikes);
2694                float midValue1 = noise[midX][midY];
2695                fill(random, noise, midX, midY, x1, midY, x2, midY, iter, spikes);
2696                float midValue2 = noise[midX][midY];
2697                noise[midX][midY] = (midValue1 + midValue2)/2f;
2698
2699                genFractalNoise(random, noise, x1, y1, midX, midY, iter + 1, spikes);
2700                genFractalNoise(random, noise, x1, midY, midX, y2, iter + 1, spikes);
2701                genFractalNoise(random, noise, midX, y1, x2, midY, iter + 1, spikes);
2702                genFractalNoise(random, noise, midX, midY, x2, y2, iter + 1, spikes);
2703        }
2704
2705        private static void fill(Random random, float[][] noise, int x, int y, int x1, int y1,
2706                        int x2, int y2, int iter, float spikes) {
2707                if (noise[x][y] == -1) {
2708                        float avg = (noise[x1][y1] + noise[x2][y2]) / 2f;
2709                        noise[x][y] = avg + ((float) Math.pow(spikes, (iter)) * (float) (random.nextFloat() - .5));
2710                }
2711        }
2712        
2713        
2714        public static float computeAngleSpan(float radius, float range) {
2715                if (range <= 1) return 180f;
2716                return (2f * radius) / (2f * (float) Math.PI * range) * 360f;
2717        }
2718        
2719        public static float computeAngleRadius(float angle, float range) {
2720                float rad = (float) Math.toRadians(angle);
2721                return rad * range;
2722        }
2723        
2724        public static float approach(float curr, float dest, float minSpeed,  float diffSpeedMult, float amount) {
2725                float diff = dest - curr;
2726                float delta = (Math.signum(diff) * minSpeed + (diff * diffSpeedMult)) * amount;
2727                
2728                if (Math.abs(delta) > Math.abs(diff)) delta = diff;
2729                return curr + delta;
2730        }
2731
2732        public static Map<Class, Method> cleanerCache = new LinkedHashMap<>();
2733        public static Map<Class, Method> cleanCache = new LinkedHashMap<>();
2734    
2735        public static void cleanBuffer(Buffer toBeDestroyed) {
2736        try {
2737                
2738                Method cleanerMethod = cleanerCache.get(toBeDestroyed.getClass());
2739                if (cleanerMethod == null) {
2740                        cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner");
2741                        if (cleanerMethod != null) {
2742                                cleanerMethod.setAccessible(true);
2743                                cleanerCache.put(toBeDestroyed.getClass(), cleanerMethod);
2744                        }
2745                }
2746                if (cleanerMethod != null) {
2747                        Object cleaner = cleanerMethod.invoke(toBeDestroyed);
2748                        if (cleaner != null) {
2749                                Method cleanMethod = cleanCache.get(cleaner.getClass());
2750                                if (cleanMethod == null) {
2751                                        cleanMethod = cleaner.getClass().getMethod("clean");
2752                                        if (cleanMethod != null) {
2753                                                cleanMethod.setAccessible(true);
2754                                                cleanCache.put(cleaner.getClass(), cleanMethod);
2755                                        }
2756                                }
2757                                if (cleanMethod != null) {
2758                                        cleanMethod.invoke(cleaner);
2759                                        Global.getLogger(Misc.class).info(String.format("Cleaned buffer (using reflection)"));
2760                                } else {
2761                                        Global.getLogger(Misc.class).warn(String.format("Buffer can not be cleaned"));
2762                                }
2763                        } else {
2764                                Global.getLogger(Misc.class).warn(String.format("Buffer can not be cleaned"));
2765                        }
2766                } else {
2767                        Global.getLogger(Misc.class).warn(String.format("Buffer can not be cleaned"));
2768                }
2769        } catch (Exception e) {
2770                Global.getLogger(Misc.class).warn(e.getMessage(), e);
2771        }
2772    }   
2773        
2774        
2775        public static float getFleetwideTotalStat(CampaignFleetAPI fleet, String dynamicMemberStatId) {
2776                float total = 0;
2777                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
2778                        if (member.isMothballed()) continue;
2779                        total += member.getStats().getDynamic().getValue(dynamicMemberStatId);
2780                }
2781                return total;
2782        }
2783        
2784        public static float getFleetwideTotalMod(CampaignFleetAPI fleet, String dynamicMemberStatId, float base) {
2785                return getFleetwideTotalMod(fleet, dynamicMemberStatId, base, null);
2786        }
2787        public static float getFleetwideTotalMod(CampaignFleetAPI fleet, String dynamicMemberStatId, float base, ShipAPI ship) {
2788                float total = 0;
2789                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
2790                        if (member.isMothballed()) continue;
2791                        if (ship != null && ship.getFleetMember() == member) {
2792                                total += ship.getMutableStats().getDynamic().getValue(dynamicMemberStatId, base);
2793                        } else {
2794                                total += member.getStats().getDynamic().getValue(dynamicMemberStatId, base);
2795                        }
2796                }
2797                return total;
2798        }
2799        
2800        public static String getStarId(PlanetAPI planet) {
2801                String starId = planet.getContainingLocation().getId();
2802                if (planet.getContainingLocation() instanceof StarSystemAPI) {
2803                        StarSystemAPI system = (StarSystemAPI) planet.getContainingLocation();
2804                        if (system.getStar() != null) {
2805                                starId = system.getStar().getId();
2806                        }
2807                }
2808                if (planet.getOrbitFocus() instanceof PlanetAPI) {
2809                        PlanetAPI parent = (PlanetAPI) planet.getOrbitFocus();
2810                        if (parent.isStar()) {
2811                                starId = parent.getId();
2812                        } else {
2813                                if (parent.getOrbitFocus() instanceof PlanetAPI) {
2814                                        parent = (PlanetAPI) parent.getOrbitFocus();
2815                                        if (parent.isStar()) {
2816                                                starId = parent.getId();
2817                                        }
2818                                }
2819                        }
2820                }
2821                return starId;
2822        }
2823        
2824        
2825//      public static enum PlanetDataForSystem {
2826//              NONE,
2827//              SEEN,
2828//              PRELIMINARY,
2829//              //PARTIAL,
2830//              FULL,
2831//      }
2832        
2833        public static SurveyLevel getMinSystemSurveyLevel(StarSystemAPI system) {
2834                //boolean some = false, all = true;
2835                SurveyLevel minLevel = SurveyLevel.FULL;
2836                boolean empty = true;
2837                for (PlanetAPI planet : system.getPlanets()) {
2838                        if (planet.isStar()) continue;
2839                        MarketAPI market = planet.getMarket();
2840                        if (market == null) continue;
2841                        
2842                        empty = false;
2843                        SurveyLevel level = market.getSurveyLevel();
2844                        if (level.ordinal() < minLevel.ordinal()) {
2845                                minLevel = level;
2846                        }
2847                }
2848                
2849                if (!system.isEnteredByPlayer() && empty) minLevel = SurveyLevel.NONE;
2850                if (system.isEnteredByPlayer() && empty) minLevel = SurveyLevel.FULL;
2851                
2852                return minLevel;
2853                
2854                        
2855//              if (all && system.isEnteredByPlayer()) return PlanetDataForSystem.FULL;
2856//              if (some) return PlanetDataForSystem.PARTIAL;
2857//              return PlanetDataForSystem.NONE;
2858        }
2859        
2860        public static boolean hasAnySurveyDataFor(StarSystemAPI system) {
2861                for (PlanetAPI planet : system.getPlanets()) {
2862                        if (planet.isStar()) continue;
2863                        MarketAPI market = planet.getMarket();
2864                        if (market == null) continue;
2865                        
2866                        SurveyLevel level = market.getSurveyLevel();
2867                        if (level != SurveyLevel.NONE) return true;
2868                }
2869                return false;
2870        }
2871        
2872        
2873        
2874        public static void setAllPlanetsKnown(String systemName) {
2875                StarSystemAPI system = Global.getSector().getStarSystem(systemName);
2876                if (system != null) {
2877                        setAllPlanetsKnown(system);
2878                } else {
2879                        throw new RuntimeException("Star system [" + systemName + "] not found");
2880                }
2881        }
2882        
2883        public static void setAllPlanetsKnown(StarSystemAPI system) {
2884                for (PlanetAPI planet : system.getPlanets()) {
2885                        if (planet.isStar()) continue;
2886                        
2887                        MarketAPI market = planet.getMarket();
2888                        if (market == null) continue;
2889                        if (!market.isPlanetConditionMarketOnly()) {
2890                                market.setSurveyLevel(SurveyLevel.FULL);
2891                        } else if (market.getSurveyLevel() == SurveyLevel.NONE) {
2892                                market.setSurveyLevel(SurveyLevel.SEEN);
2893                        }
2894                }
2895        }
2896        
2897        public static void setAllPlanetsSurveyed(StarSystemAPI system, boolean setRuinsExplored) {
2898                for (PlanetAPI planet : system.getPlanets()) {
2899                        if (planet.isStar()) continue;
2900                        
2901                        MarketAPI market = planet.getMarket();
2902                        if (market == null) continue;
2903                        
2904                        market.setSurveyLevel(SurveyLevel.FULL);
2905                        for (MarketConditionAPI mc : market.getConditions()) {
2906                                mc.setSurveyed(true);
2907                        }
2908                        
2909                        if (setRuinsExplored && Misc.hasRuins(market)) {
2910                                market.getMemoryWithoutUpdate().set("$ruinsExplored", true);
2911                        }
2912                }
2913        }
2914        
2915        public static void generatePlanetConditions(String systemName, StarAge age) {
2916                StarSystemAPI system = Global.getSector().getStarSystem(systemName);
2917                if (system != null) {
2918                        generatePlanetConditions(system, age);
2919                } else {
2920                        throw new RuntimeException("Star system [" + systemName + "] not found");
2921                }
2922        }
2923        
2924        public static void generatePlanetConditions(StarSystemAPI system, StarAge age) {
2925                for (PlanetAPI planet : system.getPlanets()) {
2926                        if (planet.isStar()) continue;
2927                        
2928                        if (planet.getMarket() != null && !planet.getMarket().getConditions().isEmpty()) continue;
2929                        
2930                        PlanetConditionGenerator.generateConditionsForPlanet(planet, age);
2931                }
2932        }
2933        
2934        
2935        public static int getEstimatedOrbitIndex(PlanetAPI planet) {
2936                Vector2f centerLoc = new Vector2f();
2937                float centerRadius = 0;
2938
2939//              if (planet.getId().toLowerCase().equals("asharu")) {
2940//                      System.out.println("sdfwefe");
2941//              }
2942                
2943                float planetRadius = planet.getRadius();
2944                PlanetAPI parent = null;
2945                PlanetAPI parentParent = null;
2946                if (planet.getOrbitFocus() instanceof PlanetAPI) {
2947                        parent = (PlanetAPI) planet.getOrbitFocus();
2948                        if (parent.getOrbitFocus() instanceof PlanetAPI) {
2949                                parentParent = (PlanetAPI) parent.getOrbitFocus();
2950                        }
2951                        if (parent.isStar()) {
2952                                centerLoc = parent.getLocation();
2953                                centerRadius = parent.getRadius();
2954                        } else if (parentParent != null && parentParent.isStar()) {
2955                                centerLoc = parentParent.getLocation();
2956                                centerRadius = parentParent.getRadius();
2957                                planetRadius = parent.getRadius();
2958                        }
2959                }
2960                
2961                float approximateExtraRadiusPerOrbit = 400f; 
2962                
2963                float dist = Misc.getDistance(centerLoc, planet.getLocation());
2964                int orbitIndex = (int) ((dist - centerRadius - planetRadius - 
2965                                                                StarSystemGenerator.STARTING_RADIUS_STAR_BASE - StarSystemGenerator.STARTING_RADIUS_STAR_RANGE * 0.5f) /
2966                                                                        (StarSystemGenerator.BASE_INCR * 1.25f + approximateExtraRadiusPerOrbit));
2967                if (orbitIndex == 0) {
2968                        orbitIndex = (int) ((dist - centerRadius - planetRadius - 
2969                                        StarSystemGenerator.STARTING_RADIUS_STAR_BASE - StarSystemGenerator.STARTING_RADIUS_STAR_RANGE * 0.5f) /
2970                                                (StarSystemGenerator.BASE_INCR * 1.25f));
2971                }
2972                if (orbitIndex < 0) orbitIndex = 0;
2973                
2974                return orbitIndex;
2975        }
2976        
2977        
2978        public static Random getRandom(long seed, int level) {
2979                if (seed == 0) return random;
2980                
2981                Random r = new Random(seed);
2982                for (int i = 0; i < level; i++) {
2983                        r.nextLong();
2984                }
2985                return new Random(r.nextLong());
2986        }
2987        
2988        
2989        public static void addSurveyDataFor(PlanetAPI planet, TextPanelAPI text) {
2990                SurveyPlugin plugin = (SurveyPlugin) Global.getSettings().getNewPluginInstance("surveyPlugin");
2991                plugin.init(Global.getSector().getPlayerFleet(), planet);
2992                
2993                String dataType = plugin.getSurveyDataType(planet);
2994                if (dataType != null) {
2995                        Global.getSector().getPlayerFleet().getCargo().addCommodity(dataType, 1);
2996                        if (text != null) {
2997                                AddRemoveCommodity.addCommodityGainText(dataType, 1, text);
2998                        }
2999                }
3000                
3001                if (planet.getSpec().hasTag(Tags.CODEX_UNLOCKABLE)) {
3002                        SharedUnlockData.get().reportPlayerAwareOfPlanet(planet.getSpec().getPlanetType(), true);
3003                }
3004                
3005                CodexUnlocker.makeAwareOfConditionsOn(planet.getMarket());
3006        }
3007        
3008        public static void setFullySurveyed(MarketAPI market, TextPanelAPI text, boolean withNotification) {
3009                //if (true) return;
3010                
3011                for (MarketConditionAPI mc : market.getConditions()) {
3012                        mc.setSurveyed(true);
3013                }
3014                market.setSurveyLevel(SurveyLevel.FULL);
3015                
3016                if (withNotification && market.getPrimaryEntity() instanceof PlanetAPI) {
3017                        PlanetAPI planet = (PlanetAPI) market.getPrimaryEntity();
3018                        String string = "Acquired full survey data for " + planet.getName() + ", " + planet.getTypeNameWithWorld().toLowerCase();
3019                        if (text != null) {
3020                                text.setFontSmallInsignia();
3021                                text.addParagraph(string, planet.getSpec().getIconColor());
3022                                text.setFontInsignia();
3023                        } else {
3024                                //Global.getSector().getCampaignUI().addMessage(string, planet.getSpec().getIconColor());
3025
3026                                MessageIntel intel = new MessageIntel("Full survey data: " + planet.getName() + ", " + planet.getTypeNameWithWorld(),
3027                                                Misc.getBasePlayerColor());//, new String[] {"" + points}, Misc.getHighlightColor());
3028                                intel.setIcon(Global.getSettings().getSpriteName("intel", "new_planet_info"));
3029                                Global.getSector().getCampaignUI().addMessage(intel, MessageClickAction.INTEL_TAB, planet);
3030                                
3031//                              CommMessageAPI message = Global.getFactory().createMessage();
3032//                              message.setSubject(string);
3033//                              //message.setSubjectColor(planet.getSpec().getIconColor());
3034//                              message.setAction(MessageClickAction.INTEL_TAB);
3035//                              message.setCustomData(planet);
3036//                              message.setAddToIntelTab(false);
3037//                              message.setSmallIcon(Global.getSettings().getSpriteName("intel_categories", "star_systems"));
3038//                              Global.getSector().getCampaignUI().addMessage(message);
3039                        }
3040                }
3041        }
3042        
3043        public static void setPreliminarySurveyed(MarketAPI market, TextPanelAPI text, boolean withNotification) {
3044                market.setSurveyLevel(SurveyLevel.PRELIMINARY);
3045                
3046                if (withNotification && market.getPrimaryEntity() instanceof PlanetAPI) {
3047                        PlanetAPI planet = (PlanetAPI) market.getPrimaryEntity();
3048                        String string = "Acquired preliminary survey data for " + planet.getName() + ", " + planet.getTypeNameWithWorld().toLowerCase();
3049                        if (text != null) {
3050                                text.setFontSmallInsignia();
3051                                text.addParagraph(string, planet.getSpec().getIconColor());
3052                                text.setFontInsignia();
3053                        } else {
3054                                //Global.getSector().getCampaignUI().addMessage(string, planet.getSpec().getIconColor());
3055                                
3056                                MessageIntel intel = new MessageIntel("Preliminary survey data: " + planet.getName() + ", " + planet.getTypeNameWithWorld(),
3057                                                Misc.getBasePlayerColor());//, new String[] {"" + points}, Misc.getHighlightColor());
3058                                intel.setIcon(Global.getSettings().getSpriteName("intel", "new_planet_info"));
3059                                Global.getSector().getCampaignUI().addMessage(intel, MessageClickAction.INTEL_TAB, planet);
3060                                
3061//                              CommMessageAPI message = Global.getFactory().createMessage();
3062//                              message.setSubject(string);
3063//                              //message.setSubjectColor(planet.getSpec().getIconColor());
3064//                              message.setAction(MessageClickAction.INTEL_TAB);
3065//                              message.setCustomData(planet);
3066//                              message.setAddToIntelTab(false);
3067//                              message.setSmallIcon(Global.getSettings().getSpriteName("intel_categories", "star_systems"));
3068//                              Global.getSector().getCampaignUI().addMessage(message);
3069                                //Global.getSector().getCampaignUI().addMessage(string, planet.getSpec().getIconColor());
3070                        }
3071                }
3072        }
3073        
3074        public static void setSeen(MarketAPI market, TextPanelAPI text, boolean withNotification) {
3075                market.setSurveyLevel(SurveyLevel.SEEN);
3076                
3077                if (withNotification && market.getPrimaryEntity() instanceof PlanetAPI) {
3078                        PlanetAPI planet = (PlanetAPI) market.getPrimaryEntity();
3079                        //String string = "Acquired preliminary survey data for " + planet.getName() + ", " + planet.getTypeNameWithWorld().toLowerCase();
3080                        String type = planet.getSpec().getName();
3081                        if (!planet.isGasGiant()) type += " World";
3082                        String string = "New planet data: " + planet.getName() + ", " + type;
3083                        if (text != null) {
3084                                text.setFontSmallInsignia();
3085                                text.addParagraph(string, planet.getSpec().getIconColor());
3086                                text.setFontInsignia();
3087                        } else {
3088                                
3089                                MessageIntel intel = new MessageIntel(string,
3090                                                Misc.getBasePlayerColor());//, new String[] {"" + points}, Misc.getHighlightColor());
3091                                intel.setIcon(Global.getSettings().getSpriteName("intel", "new_planet_info"));
3092                                Global.getSector().getCampaignUI().addMessage(intel, MessageClickAction.INTEL_TAB, planet);
3093                                
3094//                              CommMessageAPI message = Global.getFactory().createMessage();
3095//                              message.setSubject(string);
3096//                              message.setSubjectColor(planet.getSpec().getIconColor());
3097//                              message.setAction(MessageClickAction.INTEL_TAB);
3098//                              message.setCustomData(planet);
3099//                              message.setAddToIntelTab(false);
3100//                              message.setSmallIcon(Global.getSettings().getSpriteName("intel_categories", "star_systems"));
3101//                              Global.getSector().getCampaignUI().addMessage(message);
3102                                //Global.getSector().getCampaignUI().addMessage(string, planet.getSpec().getIconColor());
3103                        }
3104                }
3105        }
3106        
3107        
3108        
3109        public static String getStringWithTokenReplacement(String format, SectorEntityToken entity, Map<String, MemoryAPI> memoryMap) {
3110                return Global.getSector().getRules().performTokenReplacement(
3111                                null, format,
3112                                entity, memoryMap);
3113        }
3114        
3115        
3116        public static void renderQuadAlpha(float x, float y, float width, float height, Color color, float alphaMult) {
3117                
3118                GL11.glDisable(GL11.GL_TEXTURE_2D);
3119                GL11.glEnable(GL11.GL_BLEND);
3120                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ZERO);
3121
3122                GL11.glColor4ub((byte)color.getRed(),
3123                                                (byte)color.getGreen(),
3124                                                (byte)color.getBlue(),
3125                                                (byte)(color.getAlpha() * alphaMult));
3126                
3127                GL11.glBegin(GL11.GL_QUADS);
3128                {
3129                        GL11.glVertex2f(x, y);
3130                        GL11.glVertex2f(x, y + height);
3131                        GL11.glVertex2f(x + width, y + height);
3132                        GL11.glVertex2f(x + width, y);
3133                }
3134                GL11.glEnd();
3135        }
3136        
3137        
3138        public static void fadeAndExpire(SectorEntityToken entity) {
3139                fadeAndExpire(entity, 1f);
3140        }
3141        public static void fadeAndExpire(final SectorEntityToken entity, final float seconds) {
3142                if (entity.hasTag(Tags.FADING_OUT_AND_EXPIRING)) return;
3143                
3144                entity.addTag(Tags.NON_CLICKABLE);
3145                entity.addTag(Tags.FADING_OUT_AND_EXPIRING);
3146                //entity.getContainingLocation().addScript(new EveryFrameScript() {
3147                entity.addScript(new EveryFrameScript() {
3148                        float elapsed = 0f;
3149                        public boolean runWhilePaused() {
3150                                return false;
3151                        }
3152                        public boolean isDone() {
3153                                return entity.isExpired();
3154                        }
3155                        public void advance(float amount) {
3156                                elapsed += amount;
3157                                if (elapsed > seconds) {
3158                                        entity.setExpired(true);
3159                                }
3160                                float b = 1f - elapsed / seconds;
3161                                if (b < 0) b = 0;
3162                                if (b > 1) b = 1;
3163                                entity.forceSensorFaderBrightness(Math.min(entity.getSensorFaderBrightness(), b));
3164                                entity.setAlwaysUseSensorFaderBrightness(true);
3165                        }
3166                });
3167        }
3168        public static void fadeInOutAndExpire(final SectorEntityToken entity, final float in, final float dur, final float out) {
3169                entity.addTag(Tags.NON_CLICKABLE);
3170                entity.forceSensorFaderBrightness(0f);
3171                entity.setAlwaysUseSensorFaderBrightness(true);
3172                //entity.getContainingLocation().addScript(new EveryFrameScript() {
3173                entity.addScript(new EveryFrameScript() {
3174                        float elapsed = 0f;
3175                        public boolean runWhilePaused() {
3176                                return false;
3177                        }
3178                        public boolean isDone() {
3179                                return entity.isExpired();
3180                        }
3181                        public void advance(float amount) {
3182                                elapsed += amount;
3183                                if (elapsed > in + dur + out) {
3184                                        entity.setExpired(true);
3185                                }
3186                                float b = 1f;
3187                                if (elapsed < in) {
3188                                        b = elapsed / in;
3189                                } else if (elapsed > in + dur) {
3190                                        b = 1f - (elapsed - in - dur) / out;
3191                                }
3192                                if (b < 0) b = 0;
3193                                if (b > 1) b = 1;
3194                                entity.forceSensorFaderBrightness(Math.min(entity.getSensorFaderBrightness(), b));
3195                                entity.setAlwaysUseSensorFaderBrightness(true);
3196                        }
3197                });
3198        }
3199        
3200        public static void fadeIn(final SectorEntityToken entity, final float in) {
3201                entity.forceSensorFaderBrightness(0f);
3202                entity.setAlwaysUseSensorFaderBrightness(true);
3203                entity.addScript(new EveryFrameScript() {
3204                        float elapsed = 0f;
3205                        public boolean runWhilePaused() {
3206                                return false;
3207                        }
3208                        public boolean isDone() {
3209                                return elapsed > in;
3210                        }
3211                        public void advance(float amount) {
3212                                elapsed += amount;
3213                                if (elapsed > in) {
3214                                        entity.setAlwaysUseSensorFaderBrightness(false);
3215                                        return;
3216                                }
3217                                float b = elapsed / in;
3218                                if (b < 0) b = 0;
3219                                if (b > 1) b = 1;
3220                                entity.forceSensorFaderBrightness(Math.min(entity.getSensorFaderBrightness(), b));
3221                                entity.setAlwaysUseSensorFaderBrightness(true);
3222                        }
3223                });
3224        }
3225//      public static void fadeSensorContactAndExpire(final SectorEntityToken entity, final float seconds) {
3226//              entity.addTag(Tags.NON_CLICKABLE);
3227//              entity.addTag(Tags.FADING_OUT_AND_EXPIRING);
3228//              //entity.getContainingLocation().addScript(new EveryFrameScript() {
3229//              entity.addScript(new EveryFrameScript() {
3230//                      float elapsed = 0f;
3231//                      public boolean runWhilePaused() {
3232//                              return false;
3233//                      }
3234//                      public boolean isDone() {
3235//                              return entity.isExpired();
3236//                      }
3237//                      public void advance(float amount) {
3238//                              elapsed += amount;
3239//                              if (elapsed > seconds) {
3240//                                      entity.setExpired(true);
3241//                              }
3242//                              float b = 1f - elapsed / seconds;
3243//                              if (b < 0) b = 0;
3244//                              if (b > 1) b = 1;
3245//                              entity.forceSensorContactFaderBrightness(Math.min(entity.getSensorContactFaderBrightness(), b));
3246//                      }
3247//              });
3248//      }
3249        
3250        public static CustomCampaignEntityAPI addCargoPods(LocationAPI where, Vector2f loc) {
3251                CustomCampaignEntityAPI pods = where.addCustomEntity(null, null, Entities.CARGO_PODS, Factions.NEUTRAL);
3252                pods.getLocation().x = loc.x;
3253                pods.getLocation().y = loc.y;
3254                
3255                Vector2f vel = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f);
3256                vel.scale(5f + 10f * (float) Math.random());
3257                pods.getVelocity().set(vel);
3258                
3259                pods.setDiscoverable(null);
3260                pods.setDiscoveryXP(null);
3261                pods.setSensorProfile(1f);
3262                
3263                return pods;
3264        }
3265        
3266        
3267        public static SectorEntityToken addDebrisField(LocationAPI loc, DebrisFieldParams params, Random random) {
3268                if (random == null) random = Misc.random;
3269                SectorEntityToken debris = loc.addTerrain(Terrain.DEBRIS_FIELD, params);
3270                debris.setSensorProfile(1f);
3271                debris.setDiscoverable(true);
3272                debris.setName(((CampaignTerrainAPI)debris).getPlugin().getTerrainName());
3273                
3274//              float range = 300f + params.bandWidthInEngine * 5;
3275//              if (range > 2000) range = 2000;
3276//              debris.getDetectedRangeMod().modifyFlat("gen", range);
3277                
3278                float range = DebrisFieldTerrainPlugin.computeDetectionRange(params.bandWidthInEngine);
3279                debris.getDetectedRangeMod().modifyFlat("gen", range);
3280        
3281                debris.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, random.nextLong());
3282                
3283                // add some default salvage
3284                // most uses of this will want to clear that out and add something more specific
3285                DropData data = new DropData();
3286                data.group = Drops.BASIC;
3287                data.value = (int) ((1000 + params.bandWidthInEngine) * 5);
3288                debris.addDropValue(data); 
3289                
3290                debris.setDiscoveryXP((float)((int)(params.bandWidthInEngine * 0.2f)));
3291                if (params.baseSalvageXP <= 0) {
3292                        debris.setSalvageXP((float)((int)(params.bandWidthInEngine * 0.6f)));
3293                }
3294                
3295                return debris;
3296        }
3297
3298        public static boolean isUnboardable(FleetMemberAPI member) {
3299                if (member.getVariant() != null && member.getVariant().hasTag(Tags.VARIANT_UNBOARDABLE)) {
3300                        return true;
3301                }
3302                return isUnboardable(member.getHullSpec());
3303        }
3304        
3305        public static boolean isUnboardable(ShipHullSpecAPI hullSpec) {
3306                if (hullSpec.getHints().contains(ShipTypeHints.UNBOARDABLE)) {
3307                        for (String tag : getAllowedRecoveryTags()) {
3308                                if (hullSpec.hasTag(tag)) return false;
3309                        }
3310                        if (hullSpec.isDefaultDHull()) {
3311                                ShipHullSpecAPI parent = hullSpec.getDParentHull();
3312                                for (String tag : getAllowedRecoveryTags()) {
3313                                        if (parent.hasTag(tag)) return false;
3314                                }
3315                        }
3316                        return true;
3317                }
3318                return false;
3319        }
3320        
3321
3322        public static boolean isShipRecoverable(FleetMemberAPI member, CampaignFleetAPI recoverer, boolean own, boolean useOfficerRecovery, float chanceMult) {
3323                //Random rand = new Random(1000000 * (member.getId().hashCode() + seed + Global.getSector().getClock().getDay()));
3324                //Random rand = new Random(1000000 * (member.getId().hashCode() + Global.getSector().getClock().getDay()));
3325                if (own) {
3326                        if (!member.getVariant().getSMods().isEmpty()) {
3327                                return true;
3328                        }
3329                        if (!member.getVariant().getSModdedBuiltIns().isEmpty()) {
3330                                return true;
3331                        }
3332                        if (member.getCaptain() != null && !member.getCaptain().isDefault()) {
3333                                return true;
3334                        }
3335                }
3336                if (member.getVariant().hasTag(Tags.VARIANT_ALWAYS_RECOVERABLE)) {
3337                        return true;
3338                }
3339                Random rand = new Random(1000000 * member.getId().hashCode() + Global.getSector().getPlayerBattleSeed());
3340                //rand = new Random();
3341                float chance = Global.getSettings().getFloat("baseShipRecoveryChance");
3342                if (own) {
3343                        chance = Global.getSettings().getFloat("baseOwnShipRecoveryChance");
3344                }
3345                chance = member.getStats().getDynamic().getMod(Stats.INDIVIDUAL_SHIP_RECOVERY_MOD).computeEffective(chance);
3346                if (recoverer != null) {
3347                        chance = recoverer.getStats().getDynamic().getMod(Stats.SHIP_RECOVERY_MOD).computeEffective(chance);
3348                        if (useOfficerRecovery) {
3349                                chance = recoverer.getStats().getDynamic().getMod(Stats.OFFICER_SHIP_RECOVERY_MOD).computeEffective(chance);
3350                        }
3351                }
3352                chance *= chanceMult;
3353                
3354                if (chance < 0) chance = 0;
3355                if (chance > 1f) chance = 1f;
3356                boolean recoverable = rand.nextFloat() < chance; 
3357                
3358//              System.out.println("Recovery for " + member.getHullSpec().getHullId() + 
3359//                              "(" + member.getId().hashCode() + "): " + chance + " (" + recoverable + ")");
3360                return recoverable;
3361        }
3362
3363//      public static float computeDetectionRangeForEntity(float radius) {
3364//              float range = 300f + radius * 5f;
3365//              if (range > 2000) range = 2000;
3366//              return radius;
3367//      }
3368        
3369        
3370        
3371        public static JumpPointAPI findNearestJumpPointTo(SectorEntityToken entity) {
3372                return findNearestJumpPointTo(entity, false);
3373        }
3374        public static JumpPointAPI findNearestJumpPointTo(SectorEntityToken entity, boolean allowWormhole) {
3375                float min = Float.MAX_VALUE;
3376                JumpPointAPI result = null;
3377                List<JumpPointAPI> points = entity.getContainingLocation().getEntities(JumpPointAPI.class);
3378                
3379                for (JumpPointAPI curr : points) {
3380                        if (!allowWormhole && curr.isWormhole()) continue;
3381                        if (curr.getMemoryWithoutUpdate().getBoolean(JumpPointInteractionDialogPluginImpl.UNSTABLE_KEY)) {
3382                                continue;
3383                        }
3384                        float dist = Misc.getDistance(entity.getLocation(), curr.getLocation());
3385                        if (dist < min) {
3386                                min = dist;
3387                                result = curr;
3388                        }
3389                }
3390                return result;
3391        }
3392        
3393        public static JumpPointAPI findNearestJumpPointThatCouldBeExitedFrom(SectorEntityToken entity) {
3394                float min = Float.MAX_VALUE;
3395                JumpPointAPI result = null;
3396                List<JumpPointAPI> points = entity.getContainingLocation().getEntities(JumpPointAPI.class);
3397                
3398                for (JumpPointAPI curr : points) {
3399                        if (curr.isGasGiantAnchor() || curr.isStarAnchor()) continue;
3400                        float dist = Misc.getDistance(entity.getLocation(), curr.getLocation());
3401                        if (dist < min) {
3402                                min = dist;
3403                                result = curr;
3404                        }
3405                }
3406                return result;
3407        }
3408        
3409        public static SectorEntityToken findNearestPlanetTo(SectorEntityToken entity, boolean requireGasGiant, boolean allowStars) {
3410                float min = Float.MAX_VALUE;
3411                SectorEntityToken result = null;
3412                List<PlanetAPI> planets = entity.getContainingLocation().getPlanets();
3413                
3414                for (PlanetAPI curr : planets) {
3415                        if (requireGasGiant && !curr.isGasGiant()) continue;
3416                        if (!allowStars && curr.isStar()) continue;
3417                        float dist = Misc.getDistance(entity.getLocation(), curr.getLocation());
3418                        if (dist < min) {
3419                                min = dist;
3420                                result = curr;
3421                        }
3422                }
3423                return result;
3424        }
3425        
3426        
3427        
3428        public static final boolean shouldConvertFromStub(LocationAPI containingLocation, Vector2f location) {
3429                //if (true) return false;
3430                if (Global.getSector().getPlayerFleet() == null) return false;
3431                
3432//              if (containingLocation == null || 
3433//                              containingLocation != Global.getSector().getPlayerFleet().getContainingLocation()) return false;
3434                
3435                Vector2f stubLocInHyper = null;
3436                if (containingLocation == null || containingLocation.isHyperspace()) {
3437                        stubLocInHyper = location;
3438                } else {
3439                        stubLocInHyper = containingLocation.getLocation();
3440                }
3441
3442                Vector2f playerLoc = Global.getSector().getPlayerFleet().getLocationInHyperspace();
3443                
3444                boolean sameLoc = containingLocation != null &&
3445                                                  Global.getSector().getPlayerFleet().getContainingLocation() == containingLocation;
3446                
3447                float maxDist = 6000;
3448                if (!sameLoc) maxDist = 3000;
3449                
3450                float dist = Misc.getDistance(playerLoc, stubLocInHyper);
3451                return dist < maxDist;
3452        }
3453        
3454//      public static final boolean shouldConvertFromStub(FleetStubAPI stub) {
3455//              if (Global.getSector().getPlayerFleet() == null) return false;
3456//              
3457//              return shouldConvertFromStub(stub.getContainingLocation(), stub.getLocation());
3458//      }
3459//      
3460//      
3461//      public static final boolean shouldConvertToStub(CampaignFleetAPI fleet) {
3462//              if (fleet.getStub() == null || !fleet.isConvertToStub()) return false;
3463//              
3464//              if (Global.getSector().getPlayerFleet() == null) return true;
3465//              
3466//              Vector2f fleetLocInHyper = fleet.getLocationInHyperspace();
3467//              Vector2f playerLoc = Global.getSector().getPlayerFleet().getLocationInHyperspace();
3468//              
3469//              boolean sameLoc = fleet.getContainingLocation() != null &&
3470//                                                Global.getSector().getPlayerFleet().getContainingLocation() == fleet.getContainingLocation();
3471//              
3472//              float maxDist = 8000;
3473//              if (!sameLoc) maxDist = 4000;
3474//              
3475//              float dist = Misc.getDistance(playerLoc, fleetLocInHyper);
3476//              return dist > maxDist;
3477//      }
3478        
3479        
3480        private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
3481        
3482        /**
3483         * How Java generates a seed for a new java.util.Random() instance.
3484         * @return
3485         */
3486        public static long genRandomSeed() {
3487                return seedUniquifier() ^ System.nanoTime();
3488        }
3489        public static long seedUniquifier() {
3490                // L'Ecuyer, "Tables of Linear Congruential Generators of
3491                // Different Sizes and Good Lattice Structure", 1999
3492                for (;;) {
3493                        long current = seedUniquifier.get();
3494                        long next = current * 181783497276652981L;
3495                        //long next = current * 1181783497276652981L; // actual correct number?
3496                        if (seedUniquifier.compareAndSet(current, next)) {
3497                                return next;
3498                        }
3499                }
3500        }
3501        
3502        
3503        public static String genUID() {
3504                if (Global.getSettings() != null && Global.getSettings().isInGame() &&  
3505                                (Global.getSettings().isInCampaignState() || Global.getSettings().isGeneratingNewGame())) {
3506                        return Global.getSector().genUID(); 
3507                }
3508                return UUID.randomUUID().toString();
3509        }
3510        
3511        
3512        public static String colorsToString(List<Color> colors) {
3513                String result = "";
3514                for (Color c : colors) {
3515                        result += Integer.toHexString(c.getRGB()) + "|";
3516                }
3517                if (result.length() > 0) {
3518                        result = result.substring(0, result.length() - 1);
3519                }
3520                return result;
3521        }
3522        
3523        public static List<Color> colorsFromString(String in) {
3524                List<Color> result = new ArrayList<Color>();
3525                for (String p : in.split("\\|")) {
3526                        //result.add(new Color(Integer.parseInt(p, 16)));
3527                        result.add(new Color((int)Long.parseLong(p, 16)));
3528                }
3529                return result;
3530        }
3531        
3532        public static JumpPointAPI getJumpPointTo(PlanetAPI star) {
3533                for (Object entity : Global.getSector().getHyperspace().getEntities(JumpPointAPI.class)) {
3534                        JumpPointAPI jp = (JumpPointAPI) entity;
3535                        if (jp.getDestinationVisualEntity() == star) return jp;
3536                }
3537                return null;
3538        }
3539        
3540        @SuppressWarnings("unchecked")
3541        public static JumpPointAPI findNearestJumpPoint(SectorEntityToken from) {
3542                float min = Float.MAX_VALUE;
3543                JumpPointAPI result = null;
3544                LocationAPI location = from.getContainingLocation();
3545                List<JumpPointAPI> points = location.getEntities(JumpPointAPI.class);
3546                for (JumpPointAPI curr : points) {
3547                        float dist = Misc.getDistance(from.getLocation(), curr.getLocation());
3548                        if (dist < min) {
3549                                min = dist;
3550                                result = curr;
3551                        }
3552                }
3553                return result;
3554        }
3555        
3556        
3557        public static final String D_HULL_SUFFIX = "_default_D";
3558        public static String getDHullId(ShipHullSpecAPI spec) {
3559                String base = spec.getHullId();
3560                if (base.endsWith(D_HULL_SUFFIX)) return base;
3561                return base + D_HULL_SUFFIX;
3562        }
3563        
3564        
3565        public static HullModSpecAPI getMod(String id) {
3566                return Global.getSettings().getHullModSpec(id);
3567        }
3568        
3569        public static float getDistanceFromArc(float direction, float arc, float angle) {
3570                direction = normalizeAngle(direction);
3571                angle = normalizeAngle(angle);
3572        
3573                float dist1 = Math.abs(angle - direction) - arc/2f;
3574                float dist2 = Math.abs(360 - Math.abs(angle - direction)) - arc/2f;
3575                
3576                if (dist1 <= 0 || dist2 <= 0) return 0;
3577                
3578                return dist1 > dist2 ? dist2 : dist1;
3579        }
3580        
3581        public static void initConditionMarket(PlanetAPI planet) {
3582                if (planet.getMarket() != null) {
3583                        Global.getSector().getEconomy().removeMarket(planet.getMarket());
3584                }
3585                
3586                MarketAPI market = Global.getFactory().createMarket("market_" + planet.getId(), planet.getName(), 1);
3587                market.setPlanetConditionMarketOnly(true);
3588                market.setPrimaryEntity(planet);
3589                market.setFactionId(Factions.NEUTRAL);
3590                planet.setMarket(market);
3591                
3592                long seed = StarSystemGenerator.random.nextLong();
3593                planet.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, seed);
3594        }
3595        
3596        public static void initEconomyMarket(PlanetAPI planet) {
3597                if (planet.getMarket() != null) {
3598                        Global.getSector().getEconomy().removeMarket(planet.getMarket());
3599                }
3600                
3601                MarketAPI market = Global.getFactory().createMarket("market_" + planet.getId(), planet.getName(), 1);
3602                //market.setPlanetConditionMarketOnly(true);
3603                market.setPrimaryEntity(planet);
3604                market.setFactionId(Factions.NEUTRAL);
3605                planet.setMarket(market);
3606                Global.getSector().getEconomy().addMarket(market, true);
3607        }
3608        
3609        public static String getSurveyLevelString(SurveyLevel level, boolean withBrackets) {
3610                String str = " ";
3611                if (level == SurveyLevel.NONE) str = UNKNOWN;
3612                else if (level == SurveyLevel.SEEN) str = UNSURVEYED;
3613                else if (level == SurveyLevel.PRELIMINARY) str = PRELIMINARY;
3614                else if (level == SurveyLevel.FULL) str = FULL;
3615                
3616                if (withBrackets) {
3617                        str = "[" + str + "]";
3618                }
3619                return str;
3620        }
3621        
3622        public static String getPlanetSurveyClass(PlanetAPI planet) {
3623                SurveyPlugin plugin = (SurveyPlugin) Global.getSettings().getNewPluginInstance("surveyPlugin");
3624                String type = plugin.getSurveyDataType(planet);
3625                if (type != null) {
3626                        CommoditySpecAPI spec = Global.getSettings().getCommoditySpec(type);
3627                        String classStr = spec.getName().replaceFirst(" Survey Data", "");
3628                        return classStr;
3629                }
3630                return "Class N";
3631        }
3632        
3633        
3634        public static void setDefenderOverride(SectorEntityToken entity, DefenderDataOverride override) {
3635                entity.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_DEFENDER_OVERRIDE, override);
3636        }
3637        
3638        public static void setSalvageSpecial(SectorEntityToken entity, Object data) {
3639                entity.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SPECIAL_DATA, data);
3640//              if (data instanceof ShipRecoverySpecialData) {
3641//                      BaseSalvageSpecial.clearExtraSalvage(entity);
3642//              }
3643        }
3644        
3645        public static void setPrevSalvageSpecial(SectorEntityToken entity, Object data) {
3646                entity.getMemoryWithoutUpdate().set(MemFlags.PREV_SALVAGE_SPECIAL_DATA, data);
3647        }
3648        
3649        public static Object getSalvageSpecial(SectorEntityToken entity) {
3650                return entity.getMemoryWithoutUpdate().get(MemFlags.SALVAGE_SPECIAL_DATA);
3651        }
3652        public static Object getPrevSalvageSpecial(SectorEntityToken entity) {
3653                return entity.getMemoryWithoutUpdate().get(MemFlags.PREV_SALVAGE_SPECIAL_DATA);
3654        }
3655        
3656        
3657        
3658        
3659        public static List<StarSystemAPI> getSystemsInRange(SectorEntityToken from, Set<StarSystemAPI> exclude, boolean nonEmpty, float maxRange) {
3660                List<StarSystemAPI> systems = new ArrayList<StarSystemAPI>();
3661                
3662                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
3663                        if (exclude != null && exclude.contains(system)) continue;
3664                        
3665                        float dist = Misc.getDistance(from.getLocationInHyperspace(), system.getLocation());
3666                        if (dist > maxRange) continue;
3667                        if (nonEmpty && !systemHasPlanets(system)) continue;
3668                        
3669                        systems.add(system);
3670                }
3671                
3672                return systems;
3673        }
3674
3675//      public static boolean systemHasPulsar(StarSystemAPI system) {
3676//              return hasPulsar(system);
3677////            boolean result = (system.getStar() != null && system.getStar().getSpec().isPulsar()) ||
3678////               (system.getSecondary() != null && system.getSecondary().getSpec().isPulsar()) ||
3679////               (system.getTertiary() != null && system.getTertiary().getSpec().isPulsar());
3680////            return result;
3681//      }
3682        public static PlanetAPI getPulsarInSystem(StarSystemAPI system) {
3683                if (system.getStar() != null && system.getStar().getSpec().isPulsar()) {
3684                        return system.getStar();
3685                }
3686                if (system.getSecondary() != null && system.getSecondary().getSpec().isPulsar()) {
3687                        return system.getSecondary();
3688                }
3689                if (system.getTertiary() != null && system.getTertiary().getSpec().isPulsar()) {
3690                        return system.getTertiary();
3691                }
3692                return null;
3693        }
3694        public static boolean systemHasPlanets(StarSystemAPI system) {
3695                for (PlanetAPI p : system.getPlanets()) {
3696                        if (!p.isStar()) return true;
3697                }
3698                return false;
3699        }
3700        
3701        public static float getCampaignShipScaleMult(HullSize size) {
3702                switch (size) {
3703                case CAPITAL_SHIP:
3704                        return 0.07f;
3705                case CRUISER:
3706                        return  0.08f;
3707                case DESTROYER:
3708                        return 0.09f;
3709                case FRIGATE:
3710                        return 0.11f;
3711                case FIGHTER:
3712                        return 0.15f;
3713                case DEFAULT:
3714                        return 0.1f;
3715                }
3716                return 0.1f;
3717        }
3718        
3719        public static WeightedRandomPicker<String> createStringPicker(Object ... params) {
3720                return createStringPicker(StarSystemGenerator.random, params);
3721        }
3722        
3723        public static WeightedRandomPicker<String> createStringPicker(Random random, Object ... params) {
3724                WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random);
3725                for (int i = 0; i < params.length; i += 2) {
3726                        String item = (String) params[i];
3727                        float weight = 0f;
3728                        if (params[i+1] instanceof Float) {
3729                                weight = (Float) params[i+1];
3730                        } else if (params[i+1] instanceof Integer) {
3731                                weight = (Integer) params[i+1];
3732                        }
3733                        picker.add(item, weight);
3734                }
3735                return picker;
3736        }
3737        
3738        public static void setWarningBeaconGlowColor(SectorEntityToken beacon, Color color) {
3739                beacon.getMemoryWithoutUpdate().set(WarningBeaconEntityPlugin.GLOW_COLOR_KEY, color);
3740        }
3741        
3742        public static void setWarningBeaconPingColor(SectorEntityToken beacon, Color color) {
3743                beacon.getMemoryWithoutUpdate().set(WarningBeaconEntityPlugin.PING_COLOR_KEY, color);
3744        }
3745        
3746        public static void setWarningBeaconColors(SectorEntityToken beacon, Color glow, Color ping) {
3747                if (glow != null) setWarningBeaconGlowColor(beacon, glow);
3748                if (ping != null) setWarningBeaconPingColor(beacon, ping);
3749        }
3750        
3751
3752        public static List<CampaignFleetAPI> getNearbyFleets(SectorEntityToken from, float maxDist) {
3753                List<CampaignFleetAPI> result = new ArrayList<CampaignFleetAPI>();
3754                for (CampaignFleetAPI other : from.getContainingLocation().getFleets()) {
3755                        if (from == other) continue;
3756                        float dist = getDistance(from.getLocation(), other.getLocation());
3757                        if (dist <= maxDist) {
3758                                result.add(other);
3759                        }
3760                }
3761                return result;
3762        }
3763        
3764        public static List<CampaignFleetAPI> getVisibleFleets(SectorEntityToken from, boolean includeSensorContacts) {
3765                List<CampaignFleetAPI> result = new ArrayList<CampaignFleetAPI>();
3766                for (CampaignFleetAPI other : from.getContainingLocation().getFleets()) {
3767                        if (from == other) continue;
3768                        VisibilityLevel level = other.getVisibilityLevelTo(from);
3769                        if (level == VisibilityLevel.COMPOSITION_AND_FACTION_DETAILS || level == VisibilityLevel.COMPOSITION_DETAILS) {
3770                                result.add(other);
3771                        } else if (level == VisibilityLevel.SENSOR_CONTACT && includeSensorContacts) {
3772                                result.add(other);
3773                        }
3774                }
3775                return result;
3776        }
3777        
3778        public static boolean isSameCargo(CargoAPI baseOne, CargoAPI baseTwo) {
3779                CargoAPI one = Global.getFactory().createCargo(true);
3780                one.addAll(baseOne);
3781                one.sort();
3782                
3783                CargoAPI two = Global.getFactory().createCargo(true);
3784                two.addAll(baseTwo);
3785                two.sort();
3786        
3787
3788                if (one.getStacksCopy().size() != two.getStacksCopy().size()) return false;
3789                
3790                List<CargoStackAPI> stacks1 = one.getStacksCopy();
3791                List<CargoStackAPI> stacks2 = two.getStacksCopy();
3792                for (int i = 0; i < stacks1.size(); i++) {
3793                        CargoStackAPI s1 = stacks1.get(i);
3794                        CargoStackAPI s2 = stacks2.get(i);
3795                        
3796                        if ((s1 == null || s2 == null) && s1 != s2) return false;
3797                        if (s1.getSize() != s2.getSize()) return false;
3798                        if (s1.getType() != s2.getType()) return false;
3799                        if ((s1.getData() == null || s2.getData() == null) && s1.getData() != s2.getData()) return false;
3800                        if (!s1.getData().equals(s2.getData())) return false;
3801                }
3802                
3803                
3804                return true;
3805        }
3806        
3807        
3808        public static JumpPointAPI getDistressJumpPoint(StarSystemAPI system) {
3809                SectorEntityToken jumpPoint = null;
3810                float minDist = Float.MAX_VALUE;
3811                for (SectorEntityToken curr : system.getJumpPoints()) {
3812                        if (curr instanceof JumpPointAPI && ((JumpPointAPI)curr).isWormhole()) {
3813                                continue;
3814                        }
3815                        
3816                        float dist = Misc.getDistance(system.getCenter().getLocation(), curr.getLocation());
3817                        if (dist < minDist) {
3818                                jumpPoint = curr;
3819                                minDist = dist;
3820                        }
3821                }
3822                if (jumpPoint instanceof JumpPointAPI) {
3823                        return (JumpPointAPI) jumpPoint;
3824                }
3825                return null;
3826        }
3827        
3828        
3829        public static void clearTarget(CampaignFleetAPI fleet, boolean forgetTransponder) {
3830                fleet.setInteractionTarget(null);
3831                if (fleet.getAI() instanceof ModularFleetAIAPI) {
3832                        ModularFleetAIAPI ai = (ModularFleetAIAPI) fleet.getAI();
3833                        ai.getTacticalModule().setTarget(null);
3834                        ai.getTacticalModule().setPriorityTarget(null, 0f, false);
3835                }
3836                if (forgetTransponder) {
3837                        Misc.forgetAboutTransponder(fleet);
3838                }
3839        }
3840        
3841        public static void giveStandardReturnToSourceAssignments(CampaignFleetAPI fleet) {
3842                giveStandardReturnToSourceAssignments(fleet, true);
3843        }
3844        
3845        public static String FLEET_RETURNING_TO_DESPAWN = "$core_fleetReturningToDespawn";
3846        
3847        public static boolean isFleetReturningToDespawn(CampaignFleetAPI fleet) {
3848                return fleet.getMemoryWithoutUpdate().getBoolean(FLEET_RETURNING_TO_DESPAWN);
3849        }
3850        
3851        public static void giveStandardReturnToSourceAssignments(CampaignFleetAPI fleet, boolean withClear) {
3852                if (withClear) {
3853                        fleet.clearAssignments();
3854                }
3855                fleet.getMemoryWithoutUpdate().set(FLEET_RETURNING_TO_DESPAWN, true);
3856                MarketAPI source = Misc.getSourceMarket(fleet);
3857                if (source != null) {
3858                        fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, source.getPrimaryEntity(), 1000f, "returning to " + source.getName());
3859                        fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, source.getPrimaryEntity(), 1f + 1f * (float) Math.random());
3860                        fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, source.getPrimaryEntity(), 1000f);
3861                } else {
3862                        SectorEntityToken entity = getSourceEntity(fleet);
3863                        if (entity != null) {
3864                                fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, entity, 1000f, "returning to " + entity.getName());
3865                                fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, entity, 1f + 1f * (float) Math.random());
3866                                fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, entity, 1000f);
3867                        } else {
3868                                SectorEntityToken token = Global.getSector().getHyperspace().createToken(0, 0);
3869                                fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, token, 1000f);
3870                        }
3871                }
3872        }
3873        
3874        public static void giveStandardReturnAssignments(CampaignFleetAPI fleet, SectorEntityToken where, String text, boolean withClear) {
3875                if (withClear) {
3876                        fleet.clearAssignments();
3877                }
3878                fleet.getMemoryWithoutUpdate().set(FLEET_RETURNING_TO_DESPAWN, true);
3879                if (text == null) {
3880                        fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, where, 1000f, "returning to " + where.getName());
3881                } else {
3882                        fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, where, 1000f, text + " " + where.getName());
3883                }
3884                fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, where, 5f + 5f * (float) Math.random());
3885                fleet.addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, where, 1000f);
3886        }
3887        
3888        
3889        public static void adjustRep(float repChangeFaction, RepLevel limit, String factionId,
3890                                                                 float repChangePerson, RepLevel personLimit, PersonAPI person,
3891                                                                 TextPanelAPI text) {
3892                if (repChangeFaction != 0) {
3893                        CustomRepImpact impact = new CustomRepImpact();
3894                        impact.delta = repChangeFaction;
3895                        impact.limit = limit;
3896                        Global.getSector().adjustPlayerReputation(
3897                                        new RepActionEnvelope(RepActions.CUSTOM, impact,
3898                                                                                  null, text, true), 
3899                                                                                  factionId);
3900                        
3901                        if (person != null) {
3902                                impact.delta = repChangePerson;
3903                                impact.limit = personLimit;
3904                                Global.getSector().adjustPlayerReputation(
3905                                                new RepActionEnvelope(RepActions.CUSTOM, impact,
3906                                                                                          null, text, true), person);
3907                        }
3908                }
3909        }
3910        
3911        
3912        public static void interruptAbilitiesWithTag(CampaignFleetAPI fleet, String tag) {
3913                for (AbilityPlugin curr : fleet.getAbilities().values()) {
3914                        if (curr.isActive()) {
3915                                for (String t : curr.getSpec().getTags()) {
3916                                        if (t.equals(tag)) {
3917                                                curr.deactivate();
3918                                                break;
3919                                        }
3920                                }
3921                        }
3922                }
3923        }
3924        
3925        
3926        public static Vector2f getInterceptPoint(CampaignFleetAPI from, SectorEntityToken to) {
3927                
3928                //if (true) return new Vector2f(to.getLocation());
3929                //Vector2f v1 = new Vector2f(from.getVelocity());
3930                //Vector2f v2 = new Vector2f(to.getVelocity());
3931                Vector2f v2 = Vector2f.sub(to.getVelocity(), from.getVelocity(), new Vector2f());
3932                
3933                float s1 = from.getTravelSpeed();
3934                float s2 = v2.length();
3935                
3936                if (s1 < 10) s1 = 10;
3937                if (s2 < 10) s2 = 10;
3938                
3939                Vector2f p1 = new Vector2f(from.getLocation());
3940                Vector2f p2 = new Vector2f(to.getLocation());
3941                
3942                float dist = getDistance(p1, p2);
3943                float time = dist / s1;
3944                float maxTime = dist / s2 * 0.75f;
3945                if (time > maxTime) time = maxTime; // to ensure intercept point is never behind the from fleet
3946                
3947                Vector2f p3 = getUnitVectorAtDegreeAngle(getAngleInDegrees(v2));
3948                
3949                p3.scale(time * s2);
3950                Vector2f.add(p2, p3, p3);
3951                
3952                Vector2f overshoot = getUnitVectorAtDegreeAngle(getAngleInDegrees(p1, p3));
3953                overshoot.scale(3000f);
3954                Vector2f.add(p3, overshoot, p3);
3955                
3956                return p3;
3957        }
3958        
3959        public static Vector2f getInterceptPoint(SectorEntityToken from, SectorEntityToken to, float maxSpeedFrom) {
3960                
3961                //if (true) return new Vector2f(to.getLocation());
3962                //Vector2f v1 = new Vector2f(from.getVelocity());
3963                //Vector2f v2 = new Vector2f(to.getVelocity());
3964                Vector2f v2 = Vector2f.sub(to.getVelocity(), from.getVelocity(), new Vector2f());
3965                
3966                float s1 = maxSpeedFrom;
3967                float s2 = v2.length();
3968                
3969                if (s1 < 10) s1 = 10;
3970                if (s2 < 10) s2 = 10;
3971                
3972                Vector2f p1 = new Vector2f(from.getLocation());
3973                Vector2f p2 = new Vector2f(to.getLocation());
3974                
3975                float dist = getDistance(p1, p2);
3976                float time = dist / s1;
3977                float maxTime = dist / s2 * 0.75f;
3978                if (time > maxTime) time = maxTime; // to ensure intercept point is never behind the from fleet
3979                
3980                Vector2f p3 = getUnitVectorAtDegreeAngle(getAngleInDegrees(v2));
3981                
3982                p3.scale(time * s2);
3983                Vector2f.add(p2, p3, p3);
3984                
3985                Vector2f overshoot = getUnitVectorAtDegreeAngle(getAngleInDegrees(p1, p3));
3986                overshoot.scale(3000f);
3987                Vector2f.add(p3, overshoot, p3);
3988                
3989                return p3;
3990        }
3991
3992        public static void stopPlayerFleet() {
3993                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
3994                if (player != null) {
3995                        player.setVelocity(0, 0);
3996                }
3997        }
3998        
3999        public static String getListOfResources(Map<String, Integer> res, List<String> quantities) {
4000                List<String> list = new ArrayList<String>();
4001                for (String con : res.keySet()) {
4002                        CommoditySpecAPI spec = Global.getSettings().getCommoditySpec(con);
4003                        int qty = res.get(con);
4004                        list.add("" + qty + " " + spec.getName().toLowerCase());
4005                        quantities.add("" + qty);
4006                }
4007                return Misc.getAndJoined(list);
4008        }
4009        
4010        
4011        public static void setColor(Color color) {
4012                GL11.glColor4ub((byte)color.getRed(),
4013                                                (byte)color.getGreen(),
4014                                                (byte)color.getBlue(),
4015                                                (byte)color.getAlpha());
4016        }
4017        
4018        public static void setColor(Color color, float alphaMult) {
4019                GL11.glColor4ub((byte)color.getRed(),
4020                                                (byte)color.getGreen(),
4021                                                (byte)color.getBlue(),
4022                                                (byte)((float)color.getAlpha() * alphaMult));
4023        }
4024        
4025        
4026        public static void setColor(Color color, int alpha) {
4027                GL11.glColor4ub((byte)color.getRed(),
4028                                                (byte)color.getGreen(),
4029                                                (byte)color.getBlue(),
4030                                                (byte)alpha);
4031        }
4032        
4033        
4034        public static boolean doesMarketHaveMissionImportantPeopleOrIsMarketMissionImportant(SectorEntityToken entity) {
4035                MarketAPI market = entity.getMarket();
4036                if (market == null) return false;
4037                if (market.getPrimaryEntity() != entity) return false;
4038                
4039                if (market.getMemoryWithoutUpdate().getBoolean(MemFlags.ENTITY_MISSION_IMPORTANT)) return true;
4040                
4041                if (market != null && market.getCommDirectory() != null) {
4042                        for (CommDirectoryEntryAPI entry : market.getCommDirectory().getEntriesCopy()) {
4043                                if (entry.getType() == EntryType.PERSON && entry.getEntryData() instanceof PersonAPI) {
4044                                        PersonAPI person = (PersonAPI) entry.getEntryData();
4045                                        if (person.getMemoryWithoutUpdate().getBoolean(MemFlags.ENTITY_MISSION_IMPORTANT)) {
4046                                                return true;
4047                                        }
4048                                }
4049                        }
4050                }
4051                return false;
4052        }
4053        
4054        
4055        
4056        public static void makeImportant(SectorEntityToken entity, String reason) {
4057                makeImportant(entity.getMemoryWithoutUpdate(), reason, -1);
4058        }
4059        public static void makeImportant(SectorEntityToken entity, String reason, float dur) {
4060                makeImportant(entity.getMemoryWithoutUpdate(), reason, dur);
4061        }
4062        public static void makeImportant(PersonAPI person, String reason) {
4063                makeImportant(person.getMemoryWithoutUpdate(), reason, -1);
4064        }
4065        public static void makeImportant(PersonAPI person, String reason, float dur) {
4066                makeImportant(person.getMemoryWithoutUpdate(), reason, dur);
4067        }
4068        public static void makeImportant(MemoryAPI memory, String reason) {
4069                Misc.setFlagWithReason(memory, MemFlags.ENTITY_MISSION_IMPORTANT,
4070                                reason, true, -1);
4071        }
4072        public static void makeImportant(MemoryAPI memory, String reason, float dur) {
4073                Misc.setFlagWithReason(memory, MemFlags.ENTITY_MISSION_IMPORTANT,
4074                                reason, true, dur);
4075        }
4076        
4077        public static boolean isImportantForReason(MemoryAPI memory, String reason) {
4078                String flagKey = MemFlags.ENTITY_MISSION_IMPORTANT;
4079                return flagHasReason(memory, flagKey, reason);
4080        }
4081        
4082        public static void makeUnimportant(SectorEntityToken entity, String reason) {
4083                makeUnimportant(entity.getMemoryWithoutUpdate(), reason);
4084        }
4085        public static void makeUnimportant(PersonAPI person, String reason) {
4086                makeUnimportant(person.getMemoryWithoutUpdate(), reason);
4087        }
4088        public static void makeUnimportant(MemoryAPI memory, String reason) {
4089                Misc.setFlagWithReason(memory, MemFlags.ENTITY_MISSION_IMPORTANT,
4090                                                   reason, false, 0);
4091        }
4092        
4093        public static void cleanUpMissionMemory(MemoryAPI memory, String prefix) {
4094                List<String> unset = new ArrayList<String>();
4095                for (String key : memory.getKeys()) {
4096                        if (key.startsWith("$" + prefix)) {
4097                                unset.add(key);
4098                        }
4099                }
4100                for (String key : unset) {
4101                        memory.unset(key);
4102                }
4103                
4104                if (prefix.endsWith("_")) {
4105                        prefix = prefix.substring(0, prefix.length() - 1);
4106                }
4107                
4108                Misc.setFlagWithReason(memory, MemFlags.ENTITY_MISSION_IMPORTANT,
4109                                                       prefix, false, 0f);
4110        }
4111        
4112        
4113        public static void clearAreaAroundPlayer(float minDist) {
4114                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
4115                if (player == null) return;
4116                
4117                for (CampaignFleetAPI other : player.getContainingLocation().getFleets()) {
4118                        if (player == other) continue;
4119                        if (other.getBattle() != null) continue;
4120                        if (other.getOrbit() != null) continue;
4121                        if (!other.isHostileTo(player)) continue;
4122                        
4123                        float dist = Misc.getDistance(player.getLocation(), other.getLocation());
4124                        if (dist < minDist) {
4125                                float angle = Misc.getAngleInDegrees(player.getLocation(), other.getLocation());
4126                                Vector2f v = Misc.getUnitVectorAtDegreeAngle(angle);
4127                                v.scale(minDist);
4128                                Vector2f.add(v, other.getLocation(), v);
4129                                other.setLocation(v.x, v.y);
4130                        }
4131                }
4132        }
4133        
4134        public static long getSalvageSeed(SectorEntityToken entity) {
4135                return getSalvageSeed(entity, false);
4136        }
4137        public static long getSalvageSeed(SectorEntityToken entity, boolean nonRandom) {
4138                long seed = entity.getMemoryWithoutUpdate().getLong(MemFlags.SALVAGE_SEED);
4139                if (seed == 0) {
4140                        //seed = new Random().nextLong();
4141                        String id = entity.getId();
4142                        if (id == null) id = genUID();
4143                        if (nonRandom) {
4144                                seed = (entity.getId().hashCode() * 17000) * 1181783497276652981L;
4145                        } else {
4146                                seed = seedUniquifier() ^ (entity.getId().hashCode() * 17000);
4147                        }
4148                        Random r = new Random(seed);
4149                        for (int i = 0; i < 5; i++) {
4150                                r.nextLong();
4151                        }
4152                        long result = r.nextLong();
4153                        entity.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, result);
4154                        return result;
4155                }
4156                return seed;
4157        }
4158        
4159        
4160        public static long getNameBasedSeed(SectorEntityToken entity) {
4161                String id = entity.getName();
4162                if (id == null) id = genUID();
4163                
4164                long seed = (entity.getId().hashCode() * 17000);
4165                Random r = new Random(seed);
4166                for (int i = 0; i < 53; i++) {
4167                        r.nextLong();
4168                }
4169                long result = r.nextLong();
4170                return result;
4171        }
4172        
4173        public static void forgetAboutTransponder(CampaignFleetAPI fleet) {
4174                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
4175                if (mem.getBoolean(MemFlags.MEMORY_KEY_MAKE_HOSTILE_WHILE_TOFF)) {
4176                        mem.removeAllRequired(MemFlags.MEMORY_KEY_MAKE_HOSTILE_WHILE_TOFF);
4177                }
4178                mem.unset(MemFlags.MEMORY_KEY_SAW_PLAYER_WITH_TRANSPONDER_OFF);
4179                mem.unset(MemFlags.MEMORY_KEY_SAW_PLAYER_WITH_TRANSPONDER_ON);
4180        }
4181        
4182        public static void setAbandonedStationMarket(String marketId, SectorEntityToken station) {
4183                station.getMemoryWithoutUpdate().set("$abandonedStation", true);
4184                MarketAPI market = Global.getFactory().createMarket(marketId, station.getName(), 0);
4185                market.setSurveyLevel(SurveyLevel.FULL);
4186                market.setPrimaryEntity(station);
4187                market.setFactionId(station.getFaction().getId());
4188                market.addCondition(Conditions.ABANDONED_STATION);
4189                market.addSubmarket(Submarkets.SUBMARKET_STORAGE);
4190                market.setPlanetConditionMarketOnly(false);
4191                ((StoragePlugin)market.getSubmarket(Submarkets.SUBMARKET_STORAGE).getPlugin()).setPlayerPaidToUnlock(true);
4192                station.setMarket(market);
4193                station.getMemoryWithoutUpdate().unset("$tradeMode");
4194        }
4195        
4196        public static float getDesiredMoveDir(CampaignFleetAPI fleet) {
4197                if (fleet.getMoveDestination() == null) return 0f;
4198                
4199                if (fleet.wasSlowMoving()) {
4200                        Vector2f vel = fleet.getVelocity();
4201                        Vector2f neg = new Vector2f(vel);
4202                        neg.negate();
4203                        return getAngleInDegrees(neg);
4204                }
4205                
4206                return getAngleInDegrees(fleet.getLocation(), fleet.getMoveDestination());
4207        }
4208        
4209        public static boolean isPermaKnowsWhoPlayerIs(CampaignFleetAPI fleet) {
4210                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
4211                if (mem.contains(MemFlags.MEMORY_KEY_SAW_PLAYER_WITH_TRANSPONDER_ON) &&
4212                                mem.getExpire(MemFlags.MEMORY_KEY_SAW_PLAYER_WITH_TRANSPONDER_ON) < 0) {
4213                        return true;
4214                }
4215                return false;
4216        }
4217
4218        public static SimulatorPlugin getSimulatorPlugin() {
4219                return (SimulatorPlugin) Global.getSettings().getPlugin("simulatorPlugin");
4220        }
4221        
4222        public static ImmigrationPlugin getImmigrationPlugin(MarketAPI market) {
4223                ImmigrationPlugin plugin = Global.getSector().getPluginPicker().pickImmigrationPlugin(market);
4224                if (plugin == null) {
4225                        plugin = new CoreImmigrationPluginImpl(market);
4226                }
4227                return plugin;
4228        }
4229        
4230        public static AICoreAdminPlugin getAICoreAdminPlugin(String commodityId) {
4231                AICoreAdminPlugin plugin = Global.getSector().getPluginPicker().pickAICoreAdminPlugin(commodityId);
4232                return plugin;
4233        }
4234        
4235        public static AICoreOfficerPlugin getAICoreOfficerPlugin(String commodityId) {
4236                AICoreOfficerPlugin plugin = Global.getSector().getPluginPicker().pickAICoreOfficerPlugin(commodityId);
4237                return plugin;
4238        }
4239        
4240        public static AbandonMarketPlugin getAbandonMarketPlugin(MarketAPI market) {
4241                AbandonMarketPlugin plugin = Global.getSector().getGenericPlugins().pickPlugin(AbandonMarketPlugin.class, market);
4242                return plugin;
4243        }
4244        
4245        public static StabilizeMarketPlugin getStabilizeMarketPlugin(MarketAPI market) {
4246                StabilizeMarketPlugin plugin = Global.getSector().getGenericPlugins().pickPlugin(StabilizeMarketPlugin.class, market);
4247                return plugin;
4248        }
4249        
4250        
4251        public static FleetInflater getInflater(CampaignFleetAPI fleet, Object params) {
4252                FleetInflater plugin = Global.getSector().getPluginPicker().pickFleetInflater(fleet, params);
4253                return plugin;
4254        }
4255        
4256//      public static float getIncomingRate(MarketAPI market, float weight) {
4257//              ImmigrationPlugin plugin = getImmigrationPlugin(market);
4258//              float diff = plugin.getWeightForMarketSize(market.getSize() + 1) -
4259//                                       plugin.getWeightForMarketSize(market.getSize());
4260//              if (diff <= 0) return 0f;
4261//              
4262//              //PopulationComposition incoming = market.getIncoming();
4263//              return weight / diff;
4264//              
4265//      }
4266        
4267        public static boolean playerHasStorageAccess(MarketAPI market) {
4268                SubmarketAPI storage = market.getSubmarket(Submarkets.SUBMARKET_STORAGE);
4269                if (storage != null && storage.getPlugin().getOnClickAction(null) == OnClickAction.OPEN_SUBMARKET) {
4270                        return true;
4271                }
4272                return false;
4273        }
4274        
4275        public static float getMarketSizeProgress(MarketAPI market) {
4276                ImmigrationPlugin plugin = getImmigrationPlugin(market);
4277                float min = plugin.getWeightForMarketSize(market.getSize());
4278                float max = plugin.getWeightForMarketSize(market.getSize() + 1);
4279                
4280                float curr = market.getPopulation().getWeightValue();
4281                
4282                if (max <= min) return 0f;
4283                
4284                float f = (curr - min) / (max - min);
4285                if (f < 0) f = 0;
4286                if (f > 1) f = 1;
4287                return f;
4288        }
4289        
4290        
4291        public static float getStorageFeeFraction() {
4292                float storageFreeFraction = Global.getSettings().getFloat("storageFreeFraction");
4293                return storageFreeFraction;
4294        }
4295        
4296        public static int getStorageCostPerMonth(MarketAPI market) {
4297                return (int) (getStorageTotalValue(market) * getStorageFeeFraction());
4298        }
4299        
4300        public static SubmarketPlugin getStorage(MarketAPI market) {
4301                if (market == null) return null;
4302                SubmarketAPI submarket = market.getSubmarket(Submarkets.SUBMARKET_STORAGE);
4303                if (submarket == null) return null;
4304                return (StoragePlugin) submarket.getPlugin();
4305        }
4306        
4307        public static SubmarketPlugin getLocalResources(MarketAPI market) {
4308                SubmarketAPI submarket = market.getSubmarket(Submarkets.LOCAL_RESOURCES);
4309                if (submarket == null) return null;
4310                return submarket.getPlugin();
4311        }
4312        public static CargoAPI getStorageCargo(MarketAPI market) {
4313                if (market == null) return null;
4314                SubmarketAPI submarket = market.getSubmarket(Submarkets.SUBMARKET_STORAGE);
4315                if (submarket == null) return null;
4316                return submarket.getCargo();
4317        }
4318        
4319        public static CargoAPI getLocalResourcesCargo(MarketAPI market) {
4320                SubmarketAPI submarket = market.getSubmarket(Submarkets.LOCAL_RESOURCES);
4321                if (submarket == null) return null;
4322                return submarket.getCargo();
4323        }
4324        
4325        public static float getStorageTotalValue(MarketAPI market) {
4326                return getStorageCargoValue(market) + getStorageShipValue(market);
4327        }
4328        public static float getStorageCargoValue(MarketAPI market) {
4329                SubmarketPlugin plugin = getStorage(market);
4330                if (plugin == null) return 0f;
4331                float value = 0f;
4332                for (CargoStackAPI stack : plugin.getCargo().getStacksCopy()) {
4333                        value += stack.getSize() * stack.getBaseValuePerUnit();
4334                }
4335                return value;
4336        }
4337        
4338        public static float getStorageShipValue(MarketAPI market) {
4339                SubmarketPlugin plugin = getStorage(market);
4340                if (plugin == null) return 0f;
4341                float value = 0f;
4342                
4343                for (FleetMemberAPI member : plugin.getCargo().getMothballedShips().getMembersListCopy()) {
4344                        value += member.getBaseValue();
4345                }
4346                return value;
4347        }
4348        
4349        
4350        /**
4351         * Returns true if it added anything to the tooltip.
4352         * @return
4353         */
4354        public static boolean addStorageInfo(TooltipMakerAPI tooltip, Color color, Color dark, MarketAPI market,
4355                                                                                 //boolean showFees,
4356                                                                                 boolean includeLocalResources, boolean addSectionIfEmpty) {
4357                SubmarketPlugin storage = Misc.getStorage(market);
4358                SubmarketPlugin local = Misc.getLocalResources(market);
4359                
4360                CargoAPI cargo = Global.getFactory().createCargo(true);
4361                List<FleetMemberAPI> ships = new ArrayList<FleetMemberAPI>();
4362                if (storage != null) {
4363                        cargo.addAll(storage.getCargo());
4364                        ships.addAll(storage.getCargo().getMothballedShips().getMembersListCopy());
4365                }
4366                if (local != null && includeLocalResources) {
4367                        cargo.addAll(local.getCargo());
4368                        ships.addAll(local.getCargo().getMothballedShips().getMembersListCopy());
4369                }
4370                
4371                float opad = 15f;
4372                if (!cargo.isEmpty() || addSectionIfEmpty) {
4373                        String title = "Cargo in storage";
4374                        if (includeLocalResources && local != null) {
4375                                title = "Cargo in storage and resource stockpiles";
4376                        }
4377                        tooltip.addSectionHeading(title, color, dark, Alignment.MID, opad);
4378                        opad = 10f;
4379                        tooltip.showCargo(cargo, 10, true, opad);
4380                }
4381                
4382                if (!ships.isEmpty() || addSectionIfEmpty) {
4383                        String title = "Ships in storage";
4384                        if (includeLocalResources && local != null) {
4385                                title = "Ships in storage";
4386                        }
4387                        tooltip.addSectionHeading(title, color, dark, Alignment.MID, opad);
4388                        opad = 10f;
4389                        tooltip.showShips(ships, 10, true, opad);
4390                }
4391                
4392                if (!market.isPlayerOwned()) {
4393                        int cost = getStorageCostPerMonth(market);
4394                        if (cost > 0) {
4395                                tooltip.addPara("Monthly storage fee: %s", opad, getHighlightColor(), getDGSCredits(cost));
4396                        }
4397                }
4398                
4399                if (addSectionIfEmpty) return true;
4400                
4401                return !cargo.isEmpty() || !ships.isEmpty();
4402        }
4403        
4404        public static String getTokenReplaced(String in, SectorEntityToken entity) {
4405                in = Global.getSector().getRules().performTokenReplacement(null, in, entity, null);
4406                return in;
4407        }
4408        
4409        public static float getOutpostPenalty() {
4410                return Global.getSettings().getFloat("colonyOverMaxPenalty");
4411        }
4412
4413        
4414        public static float getAdminSalary(PersonAPI admin) {
4415                int tier = (int) admin.getMemoryWithoutUpdate().getFloat("$ome_adminTier");
4416                String salaryKey = "adminSalaryTier" + tier;
4417                float s = Global.getSettings().getInt(salaryKey);
4418                return s;
4419        }
4420        
4421        public static float getOfficerSalary(PersonAPI officer) {
4422                return getOfficerSalary(officer, Misc.isMercenary(officer));
4423        }
4424        public static float getOfficerSalary(PersonAPI officer, boolean mercenary) {
4425                int officerBase = Global.getSettings().getInt("officerSalaryBase");
4426                int officerPerLevel = Global.getSettings().getInt("officerSalaryPerLevel");
4427                
4428                float payMult = 1f;
4429                if (mercenary) {
4430                        payMult = Global.getSettings().getFloat("officerMercPayMult");
4431                }
4432                
4433                float salary = (officerBase + officer.getStats().getLevel() * officerPerLevel) * payMult;
4434                return salary;
4435        }
4436        
4437//      public static int getAccessibilityPercent(float a) {
4438//              int result = (int) Math.round(a * 100f);
4439//              if (a < 0 && result == 0) result = -1; // shipping penalty at "below zero"
4440//              return result;
4441//      }
4442        
4443        public static Map<String, Integer> variantToFPCache = new HashMap<String, Integer>();
4444        //public static Map<String, Boolean> variantToIsBaseCache = new HashMap<String, Boolean>();
4445        public static Map<String, String> variantToHullCache = new HashMap<String, String>();
4446        //public static Map<String, String> hullIdToHasTag = new HashMap<String, String>();
4447        public static String getHullIdForVariantId(String variantId) {
4448                String hull = variantToHullCache.get(variantId);
4449                if (hull != null) return hull;
4450                
4451                ShipVariantAPI variant = Global.getSettings().getVariant(variantId);
4452                hull = variant.getHullSpec().getHullId();
4453                variantToHullCache.put(variantId, hull);
4454                
4455                return hull;
4456        }
4457        
4458//      public static boolean getIsBaseForVariantId(String variantId) {
4459//              Boolean isBase = variantToIsBaseCache.get(variantId);
4460//              if (isBase != null) return isBase;
4461////            System.out.println(variantId);
4462////            if (variantId.equals("buffalo2_FS")) {
4463////                    System.out.println("wefwef");
4464////            }
4465//              ShipVariantAPI variant = Global.getSettings().getVariant(variantId);
4466//              isBase = variant.getHullSpec().hasTag(Items.TAG_BASE_BP);
4467//              variantToIsBaseCache.put(variantId, isBase);
4468//              
4469//              return isBase;
4470//      }
4471        
4472        public static int getFPForVariantId(String variantId) {
4473                Integer fp = variantToFPCache.get(variantId);
4474                if (fp != null) return fp;
4475                
4476                ShipVariantAPI variant = Global.getSettings().getVariant(variantId);
4477                fp = variant.getHullSpec().getFleetPoints();
4478                variantToFPCache.put(variantId, fp);
4479                
4480                return fp;
4481        }
4482        
4483        public static FactionPersonalityPickerPlugin getFactionPersonalityPicker() {
4484                return (FactionPersonalityPickerPlugin) Global.getSettings().getPlugin("factionPersonalityPicker");
4485        }
4486        
4487        
4488        public static float getAdjustedStrength(float fp, MarketAPI market) {
4489                fp *= Math.max(0.25f, 0.5f + Math.min(1f, Misc.getShipQuality(market)));
4490                
4491                if (market != null) {
4492                        float numShipsMult = market.getStats().getDynamic().getMod(Stats.COMBAT_FLEET_SIZE_MULT).computeEffective(0f);
4493                        fp *= numShipsMult;
4494                        
4495        //              float pts = market.getFaction().getDoctrine().getNumShips() + market.getFaction().getDoctrine().getOfficerQuality();
4496        //              fp *= 1f + (pts - 2f) / 4f;
4497                        float pts = market.getFaction().getDoctrine().getOfficerQuality();
4498                        fp *= 1f + (pts - 1f) / 4f;
4499                }
4500                return fp;
4501        }
4502        public static float getAdjustedFP(float fp, MarketAPI market) {
4503                if (market != null) {
4504                        float numShipsMult = market.getStats().getDynamic().getMod(Stats.COMBAT_FLEET_SIZE_MULT).computeEffective(0f);
4505                        fp *= numShipsMult;
4506                }
4507                return fp;
4508        }
4509        
4510        public static float getShipQuality(MarketAPI market) {
4511                return getShipQuality(market, null);
4512        }
4513        public static float getShipQuality(MarketAPI market, String factionId) {
4514                return ShipQuality.getShipQuality(market, factionId);
4515//              float quality = 0f;
4516//              
4517//              if (market != null) {
4518//                      CommodityOnMarketAPI com = market.getCommodityData(Commodities.SHIPS);
4519//                      
4520//                      SupplierData sd = com.getSupplier();
4521//                      if (sd != null && sd.getMarket() != null) {
4522//                              quality = sd.getMarket().getStats().getDynamic().getMod(Stats.PRODUCTION_QUALITY_MOD).computeEffective(0f);
4523//                              if (factionId == null && sd.getMarket().getFaction() != market.getFaction()) {
4524//                                      quality -= FleetFactoryV3.IMPORTED_QUALITY_PENALTY;
4525//                              } else if (factionId != null && !factionId.equals(sd.getMarket().getFactionId())) {
4526//                                      quality -= FleetFactoryV3.IMPORTED_QUALITY_PENALTY;
4527//                              }
4528//                      }
4529//                      
4530//                      quality += market.getStats().getDynamic().getMod(Stats.FLEET_QUALITY_MOD).computeEffective(0f);
4531//              }
4532//              
4533//              
4534//              if (factionId == null) {
4535//                      //quality += market.getFaction().getDoctrine().getShipQualityContribution();
4536//              } else {
4537//                      if (market != null) {
4538//                              quality -= market.getFaction().getDoctrine().getShipQualityContribution();
4539//                      }
4540//                      quality += Global.getSector().getFaction(factionId).getDoctrine().getShipQualityContribution();
4541//              }
4542//              
4543//              return quality;
4544        }
4545        
4546        
4547        public static ShipPickMode getShipPickMode(MarketAPI market) {
4548                return getShipPickMode(market, null);
4549        }
4550        public static ShipPickMode getShipPickMode(MarketAPI market, String factionId) {
4551                QualityData d = ShipQuality.getInstance().getQualityData(market);
4552                if (d.market != null) {
4553                        if (factionId == null && d.market.getFaction() != market.getFaction()) {
4554                                return ShipPickMode.IMPORTED;
4555                        } else if (factionId != null && !factionId.equals(d.market.getFactionId())) {
4556                                return ShipPickMode.IMPORTED;
4557                        }
4558                        return ShipPickMode.PRIORITY_THEN_ALL;
4559                }
4560                return ShipPickMode.IMPORTED;
4561        }
4562        
4563        public static boolean isBusy(CampaignFleetAPI fleet) {
4564                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.FLEET_BUSY);
4565        }
4566        
4567        public static SectorEntityToken getStationEntity(MarketAPI market, CampaignFleetAPI fleet) {
4568                for (SectorEntityToken entity : market.getConnectedEntities()) {
4569                        if (entity.hasTag(Tags.STATION)) {
4570                                CampaignFleetAPI curr = getStationFleet(entity);
4571                                if (curr != null && curr == fleet) return entity;
4572                        }
4573                }
4574                return null;
4575        }
4576        public static CampaignFleetAPI getStationFleet(MarketAPI market) {
4577                for (SectorEntityToken entity : market.getConnectedEntities()) {
4578                        if (entity.hasTag(Tags.STATION)) {
4579                                CampaignFleetAPI fleet = getStationFleet(entity);
4580                                if (fleet != null) return fleet;
4581                        }
4582                }
4583                return null;
4584        }
4585        public static CampaignFleetAPI getStationFleet(SectorEntityToken station) {
4586                if (station.hasTag(Tags.STATION)) {// && station instanceof CustomCampaignEntityAPI) {
4587                        Object test = station.getMemoryWithoutUpdate().get(MemFlags.STATION_FLEET);
4588                        if (test instanceof CampaignFleetAPI) {
4589                                return (CampaignFleetAPI) test;
4590                        }
4591                }
4592                return null;
4593        }
4594        
4595        public static CampaignFleetAPI getStationBaseFleet(MarketAPI market) {
4596                for (SectorEntityToken entity : market.getConnectedEntities()) {
4597                        if (entity.hasTag(Tags.STATION)) {
4598                                CampaignFleetAPI fleet = getStationBaseFleet(entity);
4599                                if (fleet != null) return fleet;
4600                        }
4601                }
4602                return null;
4603        }
4604        public static CampaignFleetAPI getStationBaseFleet(SectorEntityToken station) {
4605                if (station.hasTag(Tags.STATION)) {// && station instanceof CustomCampaignEntityAPI) {
4606                        Object test = station.getMemoryWithoutUpdate().get(MemFlags.STATION_BASE_FLEET);
4607                        if (test instanceof CampaignFleetAPI) {
4608                                return (CampaignFleetAPI) test;
4609                        }
4610                }
4611                return null;
4612        }
4613        
4614        public static MarketAPI getStationMarket(CampaignFleetAPI station) {
4615                Object test = station.getMemoryWithoutUpdate().get(MemFlags.STATION_MARKET);
4616                if (test instanceof MarketAPI) {
4617                        return (MarketAPI) test;
4618                }
4619                return null;
4620        }
4621        
4622        public static Industry getStationIndustry(MarketAPI market) {
4623                for (Industry ind : market.getIndustries()) {
4624                        if (ind.getSpec().hasTag(Industries.TAG_STATION)) {
4625                                return ind;
4626                        }
4627                }
4628                return null;
4629        }
4630        
4631        public static boolean isActiveModule(ShipVariantAPI variant) {
4632                boolean notActiveModule = variant.getHullSpec().getOrdnancePoints(null) <= 0 &&
4633                                                                  variant.getWeaponGroups().isEmpty() &&
4634                                                                  variant.getHullSpec().getFighterBays() <= 0;
4635                return !notActiveModule;
4636        }
4637        public static boolean isActiveModule(ShipAPI ship) {
4638                boolean notActiveModule = ship.getVariant().getHullSpec().getOrdnancePoints(null) <= 0 &&
4639                                                                  ship.getVariant().getWeaponGroups().isEmpty() &&
4640                                                                  ship.getMutableStats().getNumFighterBays().getModifiedValue() <= 0;
4641                return !notActiveModule;
4642        }
4643        
4644        public static void addCreditsMessage(String format, int credits) {
4645                Global.getSector().getCampaignUI().getMessageDisplay().addMessage(
4646                                String.format(format, Misc.getDGSCredits(credits)), getTooltipTitleAndLightHighlightColor(), Misc.getDGSCredits(credits), getHighlightColor());
4647        }
4648        
4649        
4650        public static Vector2f getSystemJumpPointHyperExitLocation(JumpPointAPI jp) {
4651                for (JumpDestination d : jp.getDestinations()) {
4652                        if (d.getDestination() != null && d.getDestination().getContainingLocation() != null &&
4653                                        d.getDestination().getContainingLocation().isHyperspace()) {
4654                                return d.getDestination().getLocation();
4655                        }
4656                }
4657                return jp.getLocationInHyperspace();
4658        }
4659        
4660        
4661        public static boolean isNear(SectorEntityToken entity, Vector2f hyperLoc) {
4662                float maxRange = Global.getSettings().getFloat("commRelayRangeAroundSystem");
4663                float dist = Misc.getDistanceLY(entity.getLocationInHyperspace(), hyperLoc);
4664                if (dist > maxRange) return false;
4665                return true;
4666        }
4667        
4668        public static float getDays(float amount) {
4669                return Global.getSector().getClock().convertToDays(amount);
4670        }
4671        
4672        public static float getProbabilityMult(float desired, float current, float deviationMult) {
4673                float deviation = desired * deviationMult;
4674                float exponent = (desired - current) / deviation;
4675                if (exponent > 4) exponent = 4;
4676                float probMult = (float) Math.pow(10f, exponent);
4677                return probMult;
4678        }
4679        
4680        public static boolean isHyperspaceAnchor(SectorEntityToken entity) {
4681                return entity != null && entity.hasTag(Tags.SYSTEM_ANCHOR);
4682        }
4683        
4684        public static StarSystemAPI getStarSystemForAnchor(SectorEntityToken anchor) {
4685                return (StarSystemAPI) anchor.getMemoryWithoutUpdate().get(MemFlags.STAR_SYSTEM_IN_ANCHOR_MEMORY);
4686        }
4687        
4688        public static void showCost(TextPanelAPI text, Color color, Color dark, String [] res, int [] quantities) {
4689                showCost(text, "Resources: consumed (available)", true, color, dark, res, quantities);
4690        }
4691        public static void showCost(TextPanelAPI text, String title, boolean withAvailable, Color color, Color dark, String [] res, int [] quantities) {
4692                showCost(text, title, withAvailable, -1f, color, dark, res, quantities, null);
4693        }
4694        public static void showCost(TextPanelAPI text, String title, boolean withAvailable, float widthOverride, Color color, Color dark, String [] res, int [] quantities, boolean [] consumed) {
4695                if (color == null) color = getBasePlayerColor();
4696                if (dark == null) dark = getDarkPlayerColor();
4697                
4698                Set<String> unmet = new HashSet<String>();
4699                Set<String> all = new LinkedHashSet<String>();
4700                
4701                CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo();
4702                
4703                for (int i = 0; i < res.length; i++) {
4704                        String commodityId = res[i];
4705                        int quantity = quantities[i];
4706                        if (quantity > cargo.getQuantity(CargoItemType.RESOURCES, commodityId)) {
4707                                unmet.add(commodityId);
4708                        }
4709                        all.add(commodityId);
4710                }
4711                
4712                float costHeight = 67;
4713                ResourceCostPanelAPI cost = text.addCostPanel(title, costHeight,
4714                                color, dark);
4715                cost.setNumberOnlyMode(true);
4716                cost.setWithBorder(false);
4717                cost.setAlignment(Alignment.LMID);
4718                
4719                if (widthOverride > 0) {
4720                        cost.setComWidthOverride(widthOverride);
4721                }
4722                
4723                boolean dgs = true;
4724                for (int i = 0; i < res.length; i++) {
4725                        String commodityId = res[i];
4726                        int required = quantities[i];
4727                        int available = (int) cargo.getCommodityQuantity(commodityId);
4728                        Color curr = color;
4729                        if (withAvailable && required > cargo.getQuantity(CargoItemType.RESOURCES, commodityId)) {
4730                                curr = Misc.getNegativeHighlightColor();
4731                        }
4732                        if (dgs) {
4733                                if (withAvailable) {
4734                                        cost.addCost(commodityId, Misc.getWithDGS(required) + " (" + Misc.getWithDGS(available) + ")", curr);
4735                                } else {
4736                                        cost.addCost(commodityId, Misc.getWithDGS(required), curr);
4737                                }
4738                                if (consumed != null && consumed[i]) {
4739                                        cost.setLastCostConsumed(true);
4740                                }
4741                        } else {
4742                                if (withAvailable) {
4743                                        cost.addCost(commodityId, "" + required + " (" + available + ")", curr);
4744                                } else {
4745                                        cost.addCost(commodityId, "" + required, curr);
4746                                }
4747                                if (consumed != null && consumed[i]) {
4748                                        cost.setLastCostConsumed(true);
4749                                }
4750                        }
4751                }
4752                cost.update();
4753        }
4754        
4755        public static boolean isPlayerFactionSetUp() {
4756                String key = "$shownFactionConfigDialog";
4757                if (Global.getSector().getMemoryWithoutUpdate().contains(key)) {
4758                        return true;
4759                }
4760                return false;
4761        }
4762
4763        public static String getFleetType(CampaignFleetAPI fleet) {
4764                return fleet.getMemoryWithoutUpdate().getString(MemFlags.MEMORY_KEY_FLEET_TYPE);
4765        }
4766        public static boolean isPatrol(CampaignFleetAPI fleet) {
4767                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PATROL_FLEET);
4768        }
4769        public static boolean isSmuggler(CampaignFleetAPI fleet) {
4770                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_SMUGGLER);
4771        }
4772        public static boolean isTrader(CampaignFleetAPI fleet) {
4773                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_TRADE_FLEET);
4774        }
4775        public static boolean isPirate(CampaignFleetAPI fleet) {
4776                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PIRATE);
4777        }
4778        public static boolean isScavenger(CampaignFleetAPI fleet) {
4779                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_SCAVENGER);
4780        }
4781        public static boolean isRaider(CampaignFleetAPI fleet) {
4782                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_RAIDER);
4783        }
4784        public static boolean isWarFleet(CampaignFleetAPI fleet) {
4785                return fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_WAR_FLEET);
4786        }
4787        
4788        
4789        /**
4790         * pair.one can be null if a stand-alone, non-market station is being returned in pair.two.
4791         * @param from
4792         * @return
4793         */
4794        public static Pair<SectorEntityToken, CampaignFleetAPI> getNearestStationInSupportRange(CampaignFleetAPI from) {
4795                SectorEntityToken closestEntity = null;
4796                CampaignFleetAPI closest = null;
4797                float minDist = Float.MAX_VALUE;
4798                for (SectorEntityToken station : from.getContainingLocation().getCustomEntitiesWithTag(Tags.STATION)) {
4799                        CampaignFleetAPI fleet = Misc.getStationFleet(station);
4800                        if (fleet == null || fleet.isEmpty()) continue;
4801                        
4802                        if (!isStationInSupportRange(from, fleet)) continue;
4803                        float dist = Misc.getDistance(from.getLocation(), station.getLocation());
4804                        
4805                        if (dist < minDist) {
4806                                closest = fleet;
4807                                closestEntity = station;
4808                                minDist = dist;
4809                        }
4810                }
4811                
4812                // remnant stations and other fleets that are in station mode, w/o a related market
4813                for (CampaignFleetAPI fleet : from.getContainingLocation().getFleets()) {
4814                        if (!fleet.isStationMode()) continue;
4815                        if (fleet.isHidden()) continue;
4816                        
4817                        if (!isStationInSupportRange(from, fleet)) continue;
4818                        float dist = Misc.getDistance(from.getLocation(), fleet.getLocation());
4819                        
4820                        if (dist < minDist) {
4821                                closest = fleet;
4822                                closestEntity = null;
4823                                minDist = dist;
4824                        }
4825                }
4826                
4827                if (closest == null) return null;
4828                
4829                return new Pair<SectorEntityToken, CampaignFleetAPI>(closestEntity, closest);
4830        }
4831        
4832        public static boolean isStationInSupportRange(CampaignFleetAPI fleet, CampaignFleetAPI station) {
4833                float check = Misc.getBattleJoinRange();
4834                float distPrimary = 10000f;
4835                MarketAPI market = getStationMarket(station);
4836                if (market != null) {
4837                        distPrimary = Misc.getDistance(fleet.getLocation(), market.getPrimaryEntity().getLocation());
4838                }
4839                float distStation = Misc.getDistance(fleet.getLocation(), station.getLocation());
4840                
4841                if (distPrimary > check && distStation > check) {
4842                        return false;
4843                }
4844                return true;
4845                
4846        }
4847        
4848        
4849        public static float getMemberStrength(FleetMemberAPI member) {
4850                return getMemberStrength(member, true, true, true);
4851        }
4852        
4853        public static float getMemberStrength(FleetMemberAPI member, boolean withHull, boolean withQuality, boolean withCaptain) {
4854                float str = member.getMemberStrength();
4855                float min = 0.25f;
4856                if (str < min) str = min;
4857                
4858                float quality = 0.5f;
4859                float sMods = 0f;
4860                if (member.getFleetData() != null && member.getFleetData().getFleet() != null) {
4861                        CampaignFleetAPI fleet = member.getFleetData().getFleet();
4862                        if (fleet.getInflater() != null && !fleet.isInflated()) {
4863                                quality = fleet.getInflater().getQuality();
4864                                sMods = fleet.getInflater().getAverageNumSMods();
4865                        } else {
4866                                BattleAPI battle = fleet.getBattle();
4867                                CampaignFleetAPI source = battle == null ? null : battle.getSourceFleet(member);
4868                                if (source != null && source.getInflater() != null &&
4869                                                !source.isInflated()) {
4870                                        quality = source.getInflater().getQuality();
4871                                        sMods = source.getInflater().getAverageNumSMods();
4872                                } else {
4873                                        float dmods = DModManager.getNumDMods(member.getVariant());
4874                                        quality = 1f - Global.getSettings().getFloat("qualityPerDMod") * dmods;
4875                                        if (quality < 0) quality = 0f;
4876                                        
4877                                        sMods = member.getVariant().getSMods().size();
4878                                }
4879                        }
4880                }
4881                
4882                if (member.isStation()) {
4883                        quality = 1f;
4884                }
4885                
4886                if (sMods > 0) {
4887                        quality += sMods * Global.getSettings().getFloat("qualityPerSMod");
4888                }
4889                
4890                float captainMult = 1f;
4891                if (member.getCaptain() != null) {
4892                        float captainLevel = (member.getCaptain().getStats().getLevel() - 1f);
4893                        if (member.isStation()) {
4894                                captainMult += captainLevel / (MAX_OFFICER_LEVEL * 2f);
4895                        } else {
4896                                captainMult += captainLevel / MAX_OFFICER_LEVEL;
4897                        }
4898                }
4899                
4900                if (withQuality) {
4901                        //str *= Math.max(0.25f, 0.5f + quality);
4902                        str *= Math.max(0.25f, 0.8f + quality * 0.4f);
4903                }
4904                if (withHull) {
4905                        str *= 0.5f + 0.5f * member.getStatus().getHullFraction();
4906                }
4907                if (withCaptain) {
4908                        str *= captainMult;
4909                }
4910                //System.out.println("Member: " + member + ", str: " + str);
4911                
4912                return str;
4913        }
4914        
4915        
4916        public static void increaseMarketHostileTimeout(MarketAPI market, float days) {
4917                MemoryAPI mem = market.getMemoryWithoutUpdate();
4918                float expire = days;
4919                if (mem.contains(MemFlags.MEMORY_KEY_PLAYER_HOSTILE_ACTIVITY_NEAR_MARKET)) {
4920                        expire += mem.getExpire(MemFlags.MEMORY_KEY_PLAYER_HOSTILE_ACTIVITY_NEAR_MARKET); 
4921                }
4922                if (expire > 180) expire = 180;
4923                if (expire > 0) {
4924                        mem.set(MemFlags.MEMORY_KEY_PLAYER_HOSTILE_ACTIVITY_NEAR_MARKET, true, expire);
4925                }
4926        }
4927        
4928        public static void removeRadioChatter(MarketAPI market) {
4929                if (market.getContainingLocation() == null) return;
4930                for (CampaignTerrainAPI terrain : market.getContainingLocation().getTerrainCopy()) {
4931                        if (Terrain.RADIO_CHATTER.equals(terrain.getType())) {
4932                                float dist = Misc.getDistance(terrain, market.getPrimaryEntity());
4933                                if (dist < 200) {
4934                                        market.getContainingLocation().removeEntity(terrain);
4935                                }
4936                        }
4937                }
4938        }
4939        
4940        
4941        public static Color getDesignTypeColor(String designType) {
4942                return Global.getSettings().getDesignTypeColor(designType);
4943        }
4944        
4945        public static Color getDesignTypeColorDim(String designType) {
4946                Color c = Global.getSettings().getDesignTypeColor(designType);
4947                return Misc.scaleColorOnly(c, 0.53f);
4948        }
4949
4950        
4951        public static LabelAPI addDesignTypePara(TooltipMakerAPI tooltip, String design, float pad) {
4952                if (design != null && !design.isEmpty()) {
4953                        return tooltip.addPara("Design type: %s", pad, Misc.getGrayColor(), Global.getSettings().getDesignTypeColor(design), design);
4954                }
4955                return null;
4956        }
4957        
4958        public static float getFleetRadiusTerrainEffectMult(CampaignFleetAPI fleet) {
4959                float min = Global.getSettings().getBaseFleetSelectionRadius() + Global.getSettings().getFleetSelectionRadiusPerUnitSize();
4960                float max = Global.getSettings().getMaxFleetSelectionRadius();
4961                float radius = fleet.getRadius();
4962                
4963                //radius = 1000;
4964
4965                float mult = (radius - min) / (max - min);
4966                if (mult > 1) mult = 1;
4967                //if (mult < 0) mult = 0;
4968                if (mult < MIN_TERRAIN_EFFECT_MULT) mult = MIN_TERRAIN_EFFECT_MULT;
4969                //mult = MIN_BURN_PENALTY + mult * BURN_PENALTY_RANGE;
4970
4971                float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
4972                mult *= skillMod;
4973                
4974                mult = Math.round(mult * 100f) / 100f;
4975                
4976                return mult;
4977        }
4978        
4979        public static float MIN_TERRAIN_EFFECT_MULT = Global.getSettings().getFloat("minTerrainEffectMult");
4980        public static float BURN_PENALTY_MULT = Global.getSettings().getFloat("standardBurnPenaltyMult");
4981        public static float getBurnMultForTerrain(CampaignFleetAPI fleet) {
4982                float mult = getFleetRadiusTerrainEffectMult(fleet);
4983                mult = (1f - BURN_PENALTY_MULT * mult);
4984                mult = Math.round(mult * 100f) / 100f;
4985                if (mult < 0.1f) mult = 0.1f;
4986//              if (mult > 1) mult = 1;
4987                return mult;
4988        }
4989                
4990        
4991        public static void addHitGlow(LocationAPI location, Vector2f loc, Vector2f vel, float size, Color color) {
4992                float dur = 1f + (float) Math.random();
4993                addHitGlow(location, loc, vel, size, dur, color);
4994        }
4995        public static void addHitGlow(LocationAPI location, Vector2f loc, Vector2f vel, float size, float dur, Color color) {
4996                location.addHitParticle(loc, vel,
4997                                size, 0.4f, dur, color);
4998                location.addHitParticle(loc, vel,
4999                                size * 0.25f, 0.4f, dur, color);
5000                location.addHitParticle(loc, vel,
5001                                size * 0.15f, 1f, dur, Color.white);
5002        }
5003        
5004        public static ParticleControllerAPI [] addGlowyParticle(LocationAPI location, Vector2f loc, Vector2f vel, float size, float rampUp, float dur, Color color) {
5005                //rampUp = 0f;
5006                //dur = 3f;
5007                
5008                ParticleControllerAPI [] result = new ParticleControllerAPI[3];
5009                
5010                result[0] = location.addParticle(loc, vel,
5011                                size, 0.4f, rampUp, dur, color);
5012                result[1] = location.addParticle(loc, vel,
5013                                size * 0.25f, 0.4f, rampUp, dur, color);
5014                result[2] = location.addParticle(loc, vel,
5015                                size * 0.15f, 1f, rampUp, dur, Color.white);
5016                
5017                return result;
5018        }
5019        
5020        
5021        public static float SAME_FACTION_BONUS = Global.getSettings().getFloat("accessibilitySameFactionBonus");
5022        public static float PER_UNIT_SHIPPING = Global.getSettings().getFloat("accessibilityPerUnitShipping");
5023        
5024        public static int getShippingCapacity(MarketAPI market, boolean inFaction) {
5025                float a = Math.round(market.getAccessibilityMod().computeEffective(0f) * 100f) / 100f;
5026                if (inFaction) {
5027                        a += SAME_FACTION_BONUS;
5028                }
5029                return (int) Math.max(0, a / PER_UNIT_SHIPPING);
5030        }
5031        
5032        public static String getStrengthDesc(float strAdjustedFP) {
5033                String strDesc;
5034                if (strAdjustedFP < 50) {
5035                        strDesc = "very weak";
5036                } else if (strAdjustedFP < 150) {
5037                        strDesc = "somewhat weak";
5038                } else if (strAdjustedFP < 300) {
5039                        strDesc = "fairly capable";
5040                } else if (strAdjustedFP < 750) {
5041                        strDesc = "fairly strong";
5042                } else if (strAdjustedFP < 1250) {
5043                        strDesc = "strong";
5044                } else {
5045                        strDesc = "very strong";
5046                }
5047                return strDesc;
5048        }
5049        
5050        public static boolean isMilitary(MarketAPI market) {
5051                return market != null && market.getMemoryWithoutUpdate().getBoolean(MemFlags.MARKET_MILITARY);
5052        }
5053        
5054        public static boolean hasHeavyIndustry(MarketAPI market) {
5055                boolean heavyIndustry = false;
5056                for (Industry curr : market.getIndustries()) {
5057                        if (curr.getSpec().hasTag(Industries.TAG_HEAVYINDUSTRY)) {
5058                                heavyIndustry = true;
5059                        }
5060                }
5061                return heavyIndustry;
5062        }
5063        
5064        public static boolean hasOrbitalStation(MarketAPI market) {
5065                for (Industry curr : market.getIndustries()) {
5066                        if (curr.getSpec().hasTag(Industries.TAG_STATION)) {
5067                                return true;
5068                        }
5069                }
5070                return false;
5071        }
5072        
5073        public static FactionAPI getClaimingFaction(SectorEntityToken planet) {
5074                if (planet.getStarSystem() != null) {
5075                        String claimedBy = planet.getStarSystem().getMemoryWithoutUpdate().getString(MemFlags.CLAIMING_FACTION);
5076                        if (claimedBy != null) {
5077                                return Global.getSector().getFaction(claimedBy);
5078                        }
5079                }
5080                
5081                int max = 0;
5082                MarketAPI result = null;
5083                List<MarketAPI> markets = Global.getSector().getEconomy().getMarkets(planet.getContainingLocation());
5084                for (MarketAPI curr : markets) {
5085                        if (curr.isHidden()) continue;
5086                        if (curr.getFaction().isPlayerFaction()) continue;
5087                        
5088                        int score = curr.getSize();
5089                        for (MarketAPI other : markets) {
5090                                if (other != curr && other.getFaction() == curr.getFaction()) score++;
5091                        }
5092                        if (isMilitary(curr)) score += 10;
5093                        if (score > max) {
5094                                JSONObject json = curr.getFaction().getCustom().optJSONObject(Factions.CUSTOM_PUNITIVE_EXPEDITION_DATA);
5095                                if (json == null) continue;
5096                                boolean territorial = json.optBoolean("territorial");
5097                                if (!territorial) continue;
5098                                
5099                                max = score;
5100                                result = curr;
5101                        }
5102                }
5103                if (result == null) return null;
5104                
5105                return result.getFaction();
5106        }
5107        
5108        
5109        public static int computeTotalShutdownRefund(MarketAPI market) {
5110                int total = 0;
5111                for (Industry industry : market.getIndustries()) {
5112                        total += computeShutdownRefund(market, industry);
5113                }
5114        
5115                // since incentives no longer work this way...
5116//              float refundFraction = Global.getSettings().getFloat("industryRefundFraction");
5117//              float incentives = market.getIncentiveCredits() * refundFraction;
5118//              total += incentives;
5119                
5120                return total;
5121        }
5122        
5123        public static int computeShutdownRefund(MarketAPI market, Industry industry) {
5124                float refund = 0;
5125                
5126                Industry upInd = null;
5127                if (industry.isUpgrading()) {
5128                        String up = industry.getSpec().getUpgrade();
5129                        if (up != null) {
5130                                upInd = market.instantiateIndustry(up);
5131                        }
5132                }
5133                if (industry.isUpgrading() && upInd != null) {
5134                        refund += upInd.getBuildCost();
5135                }
5136                
5137                float refundFraction = Global.getSettings().getFloat("industryRefundFraction");
5138                Industry curr = industry;
5139                while (curr != null) {
5140                        if (curr.isBuilding() && !curr.isUpgrading()) {
5141                                refund += curr.getBuildCost();
5142                        } else {
5143                                refund += curr.getBuildCost() * refundFraction;
5144                        }
5145                        String down = curr.getSpec().getDowngrade();
5146                        if (down != null) {
5147                                curr = market.instantiateIndustry(down);
5148                        } else {
5149                                curr = null;
5150                        }
5151                }
5152                
5153                return (int) refund;
5154        }
5155        
5156        
5157        public static SectorEntityToken addWarningBeacon(SectorEntityToken center, OrbitGap gap, String beaconTag) {
5158                CustomCampaignEntityAPI beacon = center.getContainingLocation().addCustomEntity(null, null, Entities.WARNING_BEACON, Factions.NEUTRAL);
5159                beacon.addTag(beaconTag);
5160                
5161                float radius = (gap.start + gap.end) / 2f;
5162                float orbitDays = radius / (10f + StarSystemGenerator.random.nextFloat() * 5f);
5163                beacon.setCircularOrbitPointingDown(center, StarSystemGenerator.random.nextFloat() * 360f, radius, orbitDays);
5164
5165                Color glowColor = new Color(255,200,0,255);
5166                Color pingColor = new Color(255,200,0,255);
5167                if (beaconTag.equals(Tags.BEACON_MEDIUM)) {
5168                        glowColor = new Color(250,155,0,255);
5169                        pingColor = new Color(250,155,0,255);
5170                } else if (beaconTag.equals(Tags.BEACON_HIGH)) {
5171                        glowColor = new Color(250,55,0,255);
5172                        pingColor = new Color(250,125,0,255);
5173                }
5174                Misc.setWarningBeaconColors(beacon, glowColor, pingColor);
5175                return beacon;
5176        }
5177
5178        
5179        public static CoreUITradeMode getTradeMode(MemoryAPI memory) {
5180                CoreUITradeMode mode = CoreUITradeMode.OPEN;
5181                String val = memory.getString("$tradeMode");
5182                if (val != null && !val.isEmpty()) {
5183                        mode = CoreUITradeMode.valueOf(val);
5184                }
5185                return mode;
5186        }
5187        
5188        public static boolean isSpacerStart() {
5189                return Global.getSector().getMemoryWithoutUpdate().getBoolean("$spacerStart");
5190        }
5191        
5192        public static Industry getSpaceport(MarketAPI market) {
5193                for (Industry ind : market.getIndustries()) {
5194                        if (ind.getSpec().hasTag(Industries.TAG_SPACEPORT)) {
5195                                return ind;
5196                        }
5197                }
5198                return null;
5199        }
5200        
5201        public static Color setBrightness(Color color, int brightness) {
5202                float max = color.getRed();
5203                if (color.getGreen() > max) max = color.getGreen();
5204                if (color.getBlue() > max) max = color.getBlue();
5205                float f = brightness / max;
5206                color = scaleColorSaturate(color, f);
5207                return color;
5208        }
5209        
5210        public static Color scaleColorSaturate(Color color, float factor) {
5211                int red = (int) (color.getRed() * factor);
5212                int green = (int) (color.getGreen() * factor);
5213                int blue = (int) (color.getBlue() * factor);
5214                int alpha = (int) (color.getAlpha() * factor);
5215                
5216                if (red > 255) red = 255;
5217                if (green > 255) green = 255;
5218                if (blue > 255) blue = 255;
5219                if (alpha > 255) alpha = 255;
5220                
5221                return new Color(red, green, blue, alpha);
5222        }
5223
5224        public static int getMaxOfficers(CampaignFleetAPI fleet) {
5225                int max = (int) fleet.getCommander().getStats().getOfficerNumber().getModifiedValue();
5226                return max;
5227        }
5228        public static int getNumNonMercOfficers(CampaignFleetAPI fleet) {
5229                int count = 0;
5230                for (OfficerDataAPI od : fleet.getFleetData().getOfficersCopy()) {
5231                        if (!isMercenary(od.getPerson())) {
5232                                count++;
5233                        }
5234                }
5235                return count;
5236        }
5237        
5238        public static List<OfficerDataAPI> getMercs(CampaignFleetAPI fleet) {
5239                List<OfficerDataAPI> mercs = new ArrayList<OfficerDataAPI>();
5240                for (OfficerDataAPI od : fleet.getFleetData().getOfficersCopy()) {
5241                        if (isMercenary(od.getPerson())) {
5242                                mercs.add(od);
5243                        }
5244                }
5245                return mercs;
5246        }
5247        
5248        
5249        public static int getMaxIndustries(MarketAPI market) {
5250                return (int)Math.round(market.getStats().getDynamic().getMod(Stats.MAX_INDUSTRIES).computeEffective(0));
5251        }
5252        
5253        public static int getNumIndustries(MarketAPI market) {
5254                int count = 0;
5255                for (Industry curr : market.getIndustries()) {
5256                        if (curr.isIndustry()) {
5257                                count++;
5258                        } else if (curr.isUpgrading()) {
5259                                String up = curr.getSpec().getUpgrade();
5260                                if (up != null) {
5261                                        Industry upInd = market.instantiateIndustry(up);
5262                                        if (upInd.isIndustry()) count++;
5263                                }
5264                        }
5265                }
5266                for (ConstructionQueueItem item : market.getConstructionQueue().getItems()) {
5267                        IndustrySpecAPI spec = Global.getSettings().getIndustrySpec(item.id);
5268                        if (spec.hasTag(Industries.TAG_INDUSTRY)) count++;
5269                }
5270                return count;
5271        }
5272        
5273        public static int getNumImprovedIndustries(MarketAPI market) {
5274                int count = 0;
5275                for (Industry curr : market.getIndustries()) {
5276                        if (curr.isImproved()) {
5277                                count++;
5278                        }
5279                }
5280                return count;
5281        }
5282        
5283        public static int getNumStableLocations(StarSystemAPI system) {
5284                if (system == null) return 0;
5285                int count = system.getEntitiesWithTag(Tags.STABLE_LOCATION).size();
5286                count += system.getEntitiesWithTag(Tags.OBJECTIVE).size();
5287                for (SectorEntityToken e : system.getJumpPoints()) {
5288                        if (e instanceof JumpPointAPI) {
5289                                JumpPointAPI jp = (JumpPointAPI) e;
5290                                if (jp.isWormhole()) count++;
5291                        }
5292                }
5293                return count;
5294        }
5295
5296        public static Industry getCurrentlyBeingConstructed(MarketAPI market) {
5297                for (Industry curr : market.getIndustries()) {
5298                        if (curr.getSpec().hasTag(Industries.TAG_POPULATION)) continue;
5299                        
5300                        if (curr.isBuilding() && !curr.isUpgrading()) {
5301                                return curr;
5302                        }
5303                }
5304                return null;
5305        }
5306        
5307        public static Color getRelColor(float rel) {  
5308                Color relColor = new Color(125,125,125,255);  
5309                if (rel > 1) rel = 1;  
5310                if (rel < -1) rel = -1;  
5311
5312                if (rel > 0) {  
5313                        relColor = Misc.interpolateColor(relColor, Misc.getPositiveHighlightColor(), Math.max(0.15f, rel));  
5314                } else if (rel < 0) {  
5315                        relColor = Misc.interpolateColor(relColor, Misc.getNegativeHighlightColor(), Math.max(0.15f, -rel));  
5316                }  
5317                return relColor;  
5318        } 
5319        
5320        public static MusicPlayerPlugin musicPlugin = null;
5321        public static MusicPlayerPlugin getMusicPlayerPlugin() {
5322                if (musicPlugin == null) {
5323                        musicPlugin = (MusicPlayerPlugin) Global.getSettings().getNewPluginInstance("musicPlugin");
5324                }
5325                return musicPlugin;
5326        }
5327        
5328        
5329        
5330        public static String DANGER_LEVEL_OVERRIDE = "$dangerLevelOverride";
5331        public static int getDangerLevel(CampaignFleetAPI fleet) {
5332                if (fleet.getMemoryWithoutUpdate().contains(DANGER_LEVEL_OVERRIDE)) {
5333                        return (int) fleet.getMemoryWithoutUpdate().getFloat(DANGER_LEVEL_OVERRIDE);
5334                }
5335                
5336                CampaignFleetAPI pf = Global.getSector().getPlayerFleet();
5337                
5338                float playerStr = 0f;
5339                float fleetStr = 0f;
5340                for (FleetMemberAPI member : pf.getFleetData().getMembersListCopy()) {
5341                        float strength = Misc.getMemberStrength(member, true, true, true);
5342                        playerStr += strength;
5343                }
5344                
5345                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
5346                        float strength = Misc.getMemberStrength(member, true, true, true);
5347                        fleetStr += strength;
5348                }
5349                
5350                if (playerStr > fleetStr * 3f) return 1;
5351                if (playerStr > fleetStr * 1.5f) return 2;
5352                if (playerStr > fleetStr * 0.75f) return 3;
5353                if (playerStr > fleetStr * 0.33f) return 4;
5354                return 5;
5355        }
5356        
5357        
5358        public static float getHitGlowSize(float baseSize, float baseDamage, ApplyDamageResultAPI result) {
5359                if (result == null || baseDamage <= 0) return baseSize;
5360                
5361                float sd = result.getDamageToShields() + result.getOverMaxDamageToShields();
5362                float ad = result.getTotalDamageToArmor();
5363                float hd = result.getDamageToHull();
5364                float ed = result.getEmpDamage();
5365                DamageType type = result.getType();
5366                return getHitGlowSize(baseSize, baseDamage, type, sd, ad, hd, ed);
5367        }
5368        
5369        
5370        public static float getHitGlowSize(float baseSize, float baseDamage, DamageType type, float sd, float ad, float hd, float ed) {
5371                
5372                float minBonus = 0f;
5373                if (type == DamageType.KINETIC) {
5374                        sd *= 0.5f;
5375                        //if (sd > 0) minBonus = 0.1f;
5376                } else if (type == DamageType.HIGH_EXPLOSIVE) {
5377                        ad *= 0.5f;
5378                        if (ad > 0) minBonus = 0.1f;
5379                } else if (type == DamageType.FRAGMENTATION) {
5380                        if (hd > 0) {
5381                                minBonus = 0.2f * (hd / (hd + ad));
5382                        }
5383                        sd *= 2f;
5384                        ad *= 2f;
5385                        hd *= 2f;
5386                }
5387                
5388                float totalDamage = sd + ad + hd;
5389                if (totalDamage <= 0) return baseSize;
5390                
5391                // emp damage makes the hitglow normal-sized, but not bigger
5392                if (totalDamage < baseDamage) {
5393                        totalDamage += ed;
5394                        if (totalDamage > baseDamage) {
5395                                totalDamage = baseDamage;
5396                        }
5397                }
5398                
5399                float minSize = 15f;
5400                float minMult = minSize / baseSize;
5401                minMult = Math.max(minMult, 0.67f + minBonus);
5402                if (minMult > 1) minMult = 1;
5403                
5404                float mult = totalDamage / baseDamage;
5405                if (mult < minMult) mult = minMult;
5406                
5407                float maxMult = 1.5f;
5408                if (mult > maxMult) mult = maxMult;
5409                //mult = maxMult;
5410                //mult = 1f;
5411                //System.out.println("Mult: " + mult);
5412                return baseSize * mult;
5413        }
5414        
5415        public static int getNumEliteSkills(PersonAPI person) {
5416                int count = 0;
5417                for (SkillLevelAPI sl : person.getStats().getSkillsCopy()) {
5418                        if (sl.getLevel() >= 2) count++;
5419                }
5420                return count;
5421        }
5422        
5423        
5424//      public static void spawnExtraHitGlow(Object param, Vector2f loc, Vector2f vel, float intensity) {
5425//              if (param instanceof DamagingProjectileAPI) {
5426//                      DamagingProjectileAPI proj = (DamagingProjectileAPI) param;
5427//                      ProjectileSpecAPI spec = proj.getProjectileSpec();
5428//                      if (spec != null) {
5429//                              CombatEngineAPI engine = Global.getCombatEngine();
5430//                              float base = spec.getHitGlowRadius();
5431//                              if (base == 0) base = spec.getLength() * 1.0f;
5432//                              float size = base * (1f + 1f * intensity) * 0.5f;
5433//                              if (size < 20) return;
5434//                              
5435//                              Color color = Misc.interpolateColor(spec.getFringeColor(), spec.getCoreColor(), 0.25f);
5436//                              engine.addHitParticle(loc, vel, size, 0.75f * intensity, color);
5437//                      }
5438//              } else if (param instanceof BeamAPI) {
5439//                      BeamAPI beam = (BeamAPI) param;
5440//                      CombatEngineAPI engine = Global.getCombatEngine();
5441//                      float size = beam.getHitGlowRadius() * (1f + intensity) * 0.5f;
5442//                      if (size < 20) return;
5443//                      
5444//                      Color color = Misc.interpolateColor(beam.getFringeColor(), beam.getCoreColor(), 0.25f);
5445//                      engine.addHitParticle(loc, vel, size, 0.75f * intensity, color);
5446//              }
5447//      }
5448        
5449        public static String MENTORED = "$mentored";
5450        public static boolean isMentored(PersonAPI person) {
5451                return person.getMemoryWithoutUpdate().is(MENTORED, true);
5452        }
5453        
5454        public static void setMentored(PersonAPI person, boolean mentored) {
5455                person.getMemoryWithoutUpdate().set(MENTORED, mentored);
5456        }
5457        
5458        public static final String IS_MERCENARY = "$isMercenary";
5459        public static boolean isMercenary(PersonAPI person) {
5460                return person != null && person.getMemoryWithoutUpdate().is(IS_MERCENARY, true);
5461        }
5462        
5463        public static final String MERCENARY_HIRE_TIMESTAMP = "$mercHireTS";
5464        public static void setMercHiredNow(PersonAPI person) {
5465                person.getMemoryWithoutUpdate().set(MERCENARY_HIRE_TIMESTAMP, Global.getSector().getClock().getTimestamp());
5466        }
5467        
5468        public static float getMercDaysSinceHired(PersonAPI person) {
5469                long ts = person.getMemoryWithoutUpdate().getLong(MERCENARY_HIRE_TIMESTAMP);
5470                return Global.getSector().getClock().getElapsedDaysSince(ts);
5471        }
5472        
5473        public static void setMercenary(PersonAPI person, boolean mercenary) {
5474                person.getMemoryWithoutUpdate().set(IS_MERCENARY, mercenary);
5475        }
5476        
5477        public static final String CAPTAIN_UNREMOVABLE = "$captain_unremovable";
5478        public static boolean isUnremovable(PersonAPI person) {
5479                //if (true) return true;
5480                return person != null && person.getMemoryWithoutUpdate().is(CAPTAIN_UNREMOVABLE, true);
5481        }
5482        
5483        public static void setUnremovable(PersonAPI person, boolean unremovable) {
5484                person.getMemoryWithoutUpdate().set(CAPTAIN_UNREMOVABLE, unremovable);
5485        }
5486        
5487        public static final String KEEP_CAPTAIN_ON_SHIP_RECOVERY = "$keep_captain_on_ship_recovery";
5488        public static boolean isKeepOnShipRecovery(PersonAPI person) {
5489                return person != null && person.getMemoryWithoutUpdate().is(KEEP_CAPTAIN_ON_SHIP_RECOVERY, true);
5490        }
5491        
5492        public static void setKeepOnShipRecovery(PersonAPI person, boolean keepOnRecovery) {
5493                person.getMemoryWithoutUpdate().set(KEEP_CAPTAIN_ON_SHIP_RECOVERY, keepOnRecovery);
5494        }
5495        
5496        public static boolean isAutomated(MutableShipStatsAPI stats) {
5497                if (stats == null) return false;
5498                return isAutomated(stats.getFleetMember());
5499                
5500        }
5501        public static boolean isAutomated(FleetMemberAPI member) {
5502                return member != null && member.getVariant() != null && isAutomated(member.getVariant());
5503        }
5504        public static boolean isAutomated(ShipVariantAPI variant) {
5505                return variant != null && (variant.hasHullMod(HullMods.AUTOMATED) ||
5506                                variant.hasTag(Tags.AUTOMATED) ||
5507                                (variant.getHullSpec() != null && variant.getHullSpec().hasTag(Tags.AUTOMATED)));
5508        }
5509        public static boolean isAutomated(ShipAPI ship) {
5510                if (ship == null) return false;
5511                return isAutomated(ship.getVariant()); 
5512        }
5513        
5514        
5515        public static String RECOVERY_TAGS_KEY = "$core_recoveryTags";
5516        @SuppressWarnings("unchecked")
5517        public static Set<String> getAllowedRecoveryTags() {
5518                Set<String> tags = (Set<String>) Global.getSector().getMemoryWithoutUpdate().get(RECOVERY_TAGS_KEY);
5519                if (tags == null) {
5520                        tags = new HashSet<String>();
5521                        Global.getSector().getMemoryWithoutUpdate().set(RECOVERY_TAGS_KEY, tags);
5522                }
5523                return tags;
5524        }
5525        
5526        public static int MAX_PERMA_MODS = Global.getSettings().getInt("maxPermanentHullmods");
5527        
5528        public static int getMaxPermanentMods(ShipAPI ship) {
5529                if (ship == null) return 0;
5530                return (int) Math.round(ship.getMutableStats().getDynamic().getMod(Stats.MAX_PERMANENT_HULLMODS_MOD).computeEffective(MAX_PERMA_MODS));
5531        }
5532        
5533        public static int getMaxPermanentMods(FleetMemberAPI member, MutableCharacterStatsAPI stats) {
5534                if (member == null) return 0;
5535                PersonAPI prev = member.getFleetCommanderForStats();
5536                PersonAPI fake = Global.getFactory().createPerson();
5537                fake.setStats(stats);
5538                member.setFleetCommanderForStats(fake, null);
5539                int num = (int) Math.round(member.getStats().getDynamic().getMod(Stats.MAX_PERMANENT_HULLMODS_MOD).computeEffective(MAX_PERMA_MODS));
5540                member.setFleetCommanderForStats(prev, null);
5541                return num;
5542        }
5543
5544        
5545        public static float getBuildInBonusXP(HullModSpecAPI mod, HullSize size) {
5546                //float threshold = Global.getSettings().getBonusXP("permModNoBonusXPOPThreshold");
5547                
5548                float fraction = 0f;
5549                //float cost = 0f;
5550                switch (size) {
5551                case CAPITAL_SHIP:
5552                        fraction = Global.getSettings().getBonusXP("permModCapital");
5553                        //cost = mod.getCapitalCost();
5554                        break;
5555                case CRUISER:
5556                        fraction = Global.getSettings().getBonusXP("permModCruiser");
5557                        //cost = mod.getCruiserCost();
5558                        break;
5559                case DESTROYER:
5560                        fraction = Global.getSettings().getBonusXP("permModDestroyer");
5561                        //cost = mod.getDestroyerCost();
5562                        break;
5563                case FRIGATE:
5564                        fraction = Global.getSettings().getBonusXP("permModFrigate");
5565                        //cost = mod.getFrigateCost();
5566                        break;
5567                }
5568                
5569                //float max = Global.getSettings().getBonusXP("permModMaxBonusXP");
5570                
5571//              float fraction = 0f;
5572//              if (threshold > 0) {
5573//                      fraction = max * (1f - cost / threshold);
5574//                      if (fraction < 0f) fraction = 0f;
5575//                      if (fraction > 1f) fraction = 1f;
5576//              }
5577                
5578//              MutableCharacterStatsAPI stats = Global.getSector().getPlayerStats();
5579//              fraction += stats.getDynamic().getMod(Stats.BUILD_IN_BONUS_XP_MOD).computeEffective(0);
5580                if (fraction < 0f) fraction = 0f;
5581                if (fraction > 1f) fraction = 1f;
5582                return fraction;
5583        }
5584        
5585        public static int getOPCost(HullModSpecAPI mod, HullSize size) {
5586                switch (size) {
5587                case CAPITAL_SHIP:
5588                        return mod.getCapitalCost();
5589                case CRUISER:
5590                        return mod.getCruiserCost();
5591                case DESTROYER:
5592                        return mod.getDestroyerCost();
5593                case FRIGATE:
5594                        return mod.getFrigateCost();
5595                }
5596                return mod.getFrigateCost();
5597        }
5598        
5599        public static boolean isSpecialMod(ShipVariantAPI variant, HullModSpecAPI spec) {
5600//              if (spec.getId().equals(HullMods.ANDRADA_MODS)) {
5601//                      return true;fwewefwefe
5602//              }
5603                if (spec.isHidden()) return false;
5604                if (spec.isHiddenEverywhere()) return false;
5605                if (spec.hasTag(Tags.HULLMOD_DMOD)) return false;
5606                if (!variant.getPermaMods().contains(spec.getId())) return false;
5607                if (variant.getHullSpec().getBuiltInMods().contains(spec.getId())) return false;
5608                if (!variant.getSMods().contains(spec.getId())) return false;
5609                
5610                return true;
5611        }
5612        
5613        public static boolean hasSModdableBuiltIns(ShipVariantAPI variant) {
5614                if (!CAN_SMOD_BUILT_IN || variant == null) return false;
5615                int num = 0;
5616                for (String id : variant.getHullMods()) {
5617                        HullModSpecAPI spec = Global.getSettings().getHullModSpec(id);
5618                        if (spec.isHidden()) continue;
5619                        if (spec.isHiddenEverywhere()) continue;
5620                        if (spec.hasTag(Tags.HULLMOD_DMOD)) continue;
5621                        if (variant.getHullSpec().isBuiltInMod(id) &&
5622                                        spec.getEffect().hasSModEffect() && !spec.getEffect().isSModEffectAPenalty() &&
5623                                        !variant.getSModdedBuiltIns().contains(id)) {
5624                                num++;
5625                        }
5626                }
5627                return num > 0;
5628        }
5629        public static int getCurrSpecialMods(ShipVariantAPI variant) {
5630                if (variant == null) return 0;
5631                int num = 0;
5632                for (String id : variant.getHullMods()) {
5633                        HullModSpecAPI spec = Global.getSettings().getHullModSpec(id);
5634                        if (!isSpecialMod(variant, spec)) continue;
5635//                      if (spec.isHidden()) continue;
5636//                      if (spec.isHiddenEverywhere()) continue;
5637//                      if (spec.hasTag(Tags.HULLMOD_DMOD)) continue;
5638//                      if (!variant.getPermaMods().contains(spec.getId())) continue;
5639                        num++;
5640                }
5641                return num;
5642        }
5643        
5644        public static List<HullModSpecAPI> getCurrSpecialModsList(ShipVariantAPI variant) {
5645                List<HullModSpecAPI> result = new ArrayList<HullModSpecAPI>();
5646                if (variant == null) return result;
5647                int num = 0;
5648                for (String id : variant.getHullMods()) {
5649                        HullModSpecAPI spec = Global.getSettings().getHullModSpec(id);
5650                        if (!isSpecialMod(variant, spec)) continue;
5651                        result.add(spec);
5652                }
5653                return result;
5654        }
5655        
5656        public static boolean isSlowMoving(CampaignFleetAPI fleet) {
5657                return fleet.getCurrBurnLevel() <= getGoSlowBurnLevel(fleet);
5658        }
5659        
5660        
5661        
5662        //public static float MAX_SNEAK_BURN_LEVEL = Global.getSettings().getFloat("maxSneakBurnLevel");
5663        public static float SNEAK_BURN_MULT = Global.getSettings().getFloat("sneakBurnMult");
5664        
5665        public static int getGoSlowBurnLevel(CampaignFleetAPI fleet) {
5666//              if (fleet.isPlayerFleet()) {
5667//                      System.out.println("fewfewfe");
5668//              }
5669                float bonus = fleet.getStats().getDynamic().getMod(Stats.MOVE_SLOW_SPEED_BONUS_MOD).computeEffective(0);
5670                //int burn = (int)Math.round(MAX_SNEAK_BURN_LEVEL + bonus);
5671                //int burn = (int)Math.round(fleet.getFleetData().getMinBurnLevelUnmodified() * SNEAK_BURN_MULT);
5672                int burn = (int)Math.round(fleet.getFleetData().getMinBurnLevel() * SNEAK_BURN_MULT);
5673                burn += bonus;
5674                //burn = (int) Math.min(burn, fleet.getFleetData().getBurnLevel() - 1);
5675                return burn;
5676        }
5677        
5678        
5679        public static enum FleetMemberDamageLevel {
5680                LOW,
5681                MEDIUM,
5682                HIGH,
5683        }
5684        
5685        public static void applyDamage(FleetMemberAPI member, Random random, FleetMemberDamageLevel level, 
5686                                                                   boolean withCRDamage, String crDamageId, String crDamageReason,
5687                                                                   boolean withMessage, TextPanelAPI textPanel, 
5688                                                                   String messageText) {
5689                float damageMult = 1f;
5690                switch (level) {
5691                case LOW:
5692                        damageMult = 3f;
5693                        break;
5694                case MEDIUM:
5695                        damageMult = 10f;
5696                        break;
5697                case HIGH:
5698                        damageMult = 20f;
5699                        break;
5700                }
5701                applyDamage(member, random, damageMult, withCRDamage, crDamageId, crDamageReason, 
5702                                        withMessage, textPanel, messageText);
5703        }
5704        
5705        public static void applyDamage(FleetMemberAPI member, Random random, float damageMult, 
5706                                boolean withCRDamage, String crDamageId, String crDamageReason,
5707                                boolean withMessage, TextPanelAPI textPanel, 
5708                                String messageText) {
5709                if (random == null) random = Misc.random;
5710                damageMult *= 0.75f + random.nextFloat() * 0.5f;
5711                
5712//              float hitStrength = 0f; 
5713//              hitStrength += member.getHullSpec().getArmorRating() * 0.1f;
5714//              hitStrength *= damageMult;
5715                
5716                // hull damage going to be overridden by hullDamageFraction, anyway
5717                // so just want enough hitStrength to visibly damage the armor
5718                float hitStrength = member.getHullSpec().getArmorRating() * 2f;
5719                
5720                float hullDamageFraction = 0.025f * damageMult;
5721                float max = 0.5f + random.nextFloat() * 0.1f;
5722                float min = 0.01f + random.nextFloat() * 0.04f;
5723                if (hullDamageFraction > max) hullDamageFraction = max;
5724                if (hullDamageFraction < min) hullDamageFraction = min;
5725                
5726                if (hitStrength > 0) {
5727                        float numHits = 3f;
5728                        for (int i = 0; i < numHits; i++) {
5729                                member.getStatus().applyDamage(hitStrength / numHits, hullDamageFraction / numHits);
5730                        }       
5731                        if (member.getStatus().getHullFraction() < 0.01f) {
5732                                member.getStatus().setHullFraction(0.01f);
5733                        }
5734                        
5735                        boolean isPF = member != null && member.getFleetData() != null &&
5736                                        member.getFleetData().getFleet() != null && member.getFleetData().getFleet().isPlayerFleet();
5737                        
5738                        if (withCRDamage) {
5739                                float crPerDep = member.getDeployCost();
5740                                float currCR = member.getRepairTracker().getBaseCR();
5741                                float crDamage = Math.min(currCR, crPerDep * 0.1f * damageMult);
5742                                if (crDamage > 0) {
5743                                        if (isPF) {
5744                                                member.getRepairTracker().applyCREvent(-crDamage, crDamageId, crDamageReason);
5745                                        } else {
5746                                                member.getRepairTracker().applyCREvent(-crDamage, null, null);
5747                                        }
5748                                }
5749                        }
5750                        
5751                        if (withMessage && isPF) {
5752                                MessageIntel intel = new MessageIntel(messageText,
5753                                                                                Misc.getNegativeHighlightColor());
5754                                intel.setIcon(Global.getSettings().getSpriteName("intel", "damage_report"));
5755                                
5756                                if (textPanel != null) {
5757                                        Global.getSector().getIntelManager().addIntelToTextPanel(intel, textPanel);
5758                                } else {
5759                                        Global.getSector().getCampaignUI().addMessage(intel, MessageClickAction.REFIT_TAB, member);
5760                                }
5761                        }
5762                }
5763        }
5764        
5765        public static float getBonusXPForRecovering(FleetMemberAPI member) {
5766                float ownedShip = Global.getSettings().getBonusXP("recoverOwnedShip");
5767                float threshold = Global.getSettings().getBonusXP("recoverNoBonusXPDeploymentPoints");
5768                
5769                if (member.getOwner() == 0) {
5770                        return ownedShip;
5771                }
5772                
5773                float f = 1f - member.getDeploymentPointsCost() / threshold;
5774                if (f < 0) f = 0;
5775                if (f > 1) f = 1;
5776                
5777                return f;
5778        }
5779        
5780        public static float [] getBonusXPForScuttling(FleetMemberAPI member) {
5781                float points = 0f;
5782                float xp = 0f;
5783                for (SModRecord record : PlaythroughLog.getInstance().getSModsInstalled()) {
5784                        //if (member.getId() != null && member.getId().equals(record.getMemberId())) {
5785                        if (member == record.getMember() && record.getMember() != null) {
5786                                points += record.getSPSpent();
5787                                xp += record.getBonusXPFractionGained() * record.getSPSpent();
5788                        }
5789                }
5790                if (points > 0) {
5791                        return new float[] {points, 1f - xp/points};
5792                }
5793                return new float[] {0f, 0f};
5794        }
5795        
5796        public static float getSpawnFPMult(CampaignFleetAPI fleet) {
5797                float mult = fleet.getMemoryWithoutUpdate().getFloat(FleetFactoryV3.KEY_SPAWN_FP_MULT);
5798                if (mult == 0) mult = 1f;
5799                return mult;
5800        }
5801        
5802        public static void setSpawnFPMult(CampaignFleetAPI fleet, float mult) {
5803                fleet.getMemoryWithoutUpdate().set(FleetFactoryV3.KEY_SPAWN_FP_MULT, mult);
5804        }
5805        
5806        public static boolean isDecentralized(FactionAPI faction) {
5807                return faction != null && faction.getCustomBoolean(Factions.CUSTOM_DECENTRALIZED);
5808        }
5809        
5810        public static String getPersonalityName(PersonAPI person) {
5811                String personalityName = person.getPersonalityAPI().getDisplayName();
5812                if (person.isAICore()) {
5813                        if (Personalities.RECKLESS.equals(person.getPersonalityAPI().getId())) {
5814                                personalityName = "Fearless";
5815                        }
5816                }
5817                return personalityName;
5818        }
5819        
5820        public static String LAST_RAIDED_AT = "$lastRaidedAt";
5821        public static void setRaidedTimestamp(MarketAPI market) {
5822                market.getMemoryWithoutUpdate().set(LAST_RAIDED_AT, Global.getSector().getClock().getTimestamp());
5823        }
5824        
5825        public static float getDaysSinceLastRaided(MarketAPI market) {
5826                Long ts = market.getMemoryWithoutUpdate().getLong(LAST_RAIDED_AT);
5827                if (ts == null) return Float.MAX_VALUE;
5828                return Global.getSector().getClock().getElapsedDaysSince(ts);
5829        }
5830        
5831        public static int computeEconUnitChangeFromTradeModChange(CommodityOnMarketAPI com, int quantity) {
5832                float currQty = com.getCombinedTradeModQuantity();
5833                int currMod = (int) com.getModValueForQuantity(currQty);
5834                
5835                float quantityWithTX = com.getTradeMod().getModifiedValue() + quantity + 
5836                                                                Math.max(com.getTradeModPlus().getModifiedValue(), 0) +
5837                                                                Math.min(com.getTradeModMinus().getModifiedValue(), 0);
5838                
5839                int newMod = (int) com.getModValueForQuantity(quantityWithTX);
5840                
5841                int diff = newMod - currMod;
5842                
5843                return diff;
5844        }
5845        
5846        public static void affectAvailabilityWithinReason(CommodityOnMarketAPI com, int quantity) {
5847                int units = computeEconUnitChangeFromTradeModChange(com, quantity);
5848                int maxUnits = Math.min(3, Math.max(com.getMaxDemand(), com.getMaxSupply()));
5849                if (Math.abs(units) > maxUnits) {
5850                        int sign = (int) Math.signum(quantity);
5851                        quantity = (int) Math.round(com.getQuantityForModValue(maxUnits));
5852                        quantity *= sign;
5853                }
5854                com.addTradeMod("mod_" + Misc.genUID(), quantity, BaseSubmarketPlugin.TRADE_IMPACT_DAYS);
5855        }
5856        
5857        
5858        public static boolean isOpenlyPopulated(StarSystemAPI system) {
5859                for (MarketAPI market : Misc.getMarketsInLocation(system)) {
5860                        if (!market.isHidden()) return true;
5861                }
5862                return false;
5863        }
5864        
5865        
5866        public static boolean hasAtLeastOneOfTags(Collection<String> tags, String ... other) {
5867                for (String tag : other) {
5868                        if (tags.contains(tag)) return true;
5869                }
5870                return false;
5871        }
5872        
5873        
5874        
5875//      public static boolean isUnpopulatedPlanet(PlanetAPI planet) {
5876//              if (planet.isStar() || 
5877//                              planet.getMarket() == null || 
5878//                              !planet.getMarket().isPlanetConditionMarketOnly()) {
5879//                      return false;
5880//              }
5881//              return true;
5882//      }
5883        
5884        public static boolean hasUnexploredRuins(MarketAPI market) {
5885                return market != null && market.isPlanetConditionMarketOnly() &&
5886                        hasRuins(market) && !market.getMemoryWithoutUpdate().getBoolean("$ruinsExplored");
5887        }
5888        public static boolean hasRuins(MarketAPI market) {
5889                return market != null && 
5890                           (market.hasCondition(Conditions.RUINS_SCATTERED) || 
5891                           market.hasCondition(Conditions.RUINS_WIDESPREAD) ||
5892                           market.hasCondition(Conditions.RUINS_EXTENSIVE) ||
5893                           market.hasCondition(Conditions.RUINS_VAST));
5894        }
5895        
5896        public static MarketConditionSpecAPI getRuinsSpec(MarketAPI market) {
5897                String id = getRuinsType(market);
5898                return Global.getSettings().getMarketConditionSpec(id);
5899        }
5900        
5901        /**
5902         * Assumes the market *does* have ruins.
5903         * @param market
5904         * @return
5905         */
5906        public static String getRuinsType(MarketAPI market) {
5907                if (market == null) return Conditions.RUINS_SCATTERED;
5908                if (market.hasCondition(Conditions.RUINS_SCATTERED)) return Conditions.RUINS_SCATTERED; 
5909                if (market.hasCondition(Conditions.RUINS_WIDESPREAD)) return Conditions.RUINS_WIDESPREAD; 
5910                if (market.hasCondition(Conditions.RUINS_EXTENSIVE)) return Conditions.RUINS_EXTENSIVE; 
5911                if (market.hasCondition(Conditions.RUINS_VAST)) return Conditions.RUINS_VAST;
5912                return Conditions.RUINS_SCATTERED;
5913        }
5914        
5915        public static boolean hasFarmland(MarketAPI market) {
5916                return market != null && 
5917                                (market.hasCondition(Conditions.FARMLAND_POOR) || 
5918                                market.hasCondition(Conditions.FARMLAND_ADEQUATE) ||
5919                                market.hasCondition(Conditions.FARMLAND_RICH) ||
5920                                market.hasCondition(Conditions.FARMLAND_BOUNTIFUL));
5921        }
5922        
5923        
5924        public static String DEFEAT_TRIGGERS = "$defeatTriggers";
5925        public static void addDefeatTrigger(CampaignFleetAPI fleet, String trigger) {
5926                List<String> triggers = getDefeatTriggers(fleet, true);
5927                triggers.add(trigger);
5928        }
5929        
5930        public static void removeDefeatTrigger(CampaignFleetAPI fleet, String trigger) {
5931                List<String> triggers = getDefeatTriggers(fleet, false);
5932                if (triggers != null) {
5933                        triggers.remove(trigger);
5934                        clearDefeatTriggersIfNeeded(fleet);
5935                }
5936        }
5937        
5938        @SuppressWarnings("unchecked")
5939        public static List<String> getDefeatTriggers(CampaignFleetAPI fleet, boolean createIfNecessary) {
5940                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
5941                List<String> triggers = null;
5942                if (!mem.contains(DEFEAT_TRIGGERS)) {
5943                        if (!createIfNecessary) return null;
5944                        triggers = new ArrayList<String>();
5945                        mem.set(DEFEAT_TRIGGERS, triggers);
5946                } else {
5947                        triggers = (List<String>) mem.get(DEFEAT_TRIGGERS);
5948                }
5949                return triggers;
5950        }
5951        
5952        public static void clearDefeatTriggersIfNeeded(CampaignFleetAPI fleet) {
5953                List<String> triggers = getDefeatTriggers(fleet, false);
5954                if (triggers != null && triggers.isEmpty()) {
5955                        MemoryAPI mem = fleet.getMemoryWithoutUpdate();
5956                        mem.unset(DEFEAT_TRIGGERS);
5957                }
5958        }
5959        
5960        public static boolean shouldShowDamageFloaty(ShipAPI source, ShipAPI target) {
5961                if (target == null || !Global.getCombatEngine().getShips().contains(target)) {
5962                        return false;
5963                }
5964                CombatEngineAPI engine = Global.getCombatEngine();
5965                ShipAPI playerShip = engine.getPlayerShip();
5966                
5967                boolean sourceIsPlayerShipWing = false;
5968                sourceIsPlayerShipWing = source != null && source.getWing() != null && 
5969                                                                 source.getWing().getSourceShip() == playerShip;
5970                
5971                CombatEntityAPI followedEntity = engine.getCombatUI().getEntityToFollowV2();
5972                boolean showFloaty = target == playerShip || // this is the player's ship
5973                target == followedEntity || // getting video feed from this ship
5974                                                         source == playerShip || // the damage came from the player's ship
5975                                                         sourceIsPlayerShipWing ||
5976                                                         target == playerShip.getShipTarget() || // the ship is the player ship's target
5977                                                         engine.hasAttachedFloaty(target); // the ship already has a floaty on it, meaning the player is likely looking at it
5978                showFloaty = showFloaty && !target.isFighter(); // no floaties on fighters
5979                showFloaty = showFloaty && Global.getSettings().isShowDamageFloaties();
5980                return showFloaty;
5981        }
5982
5983//      
5984//       public static Vector2f cubeBezier(Vector2f p0, Vector2f p1, Vector2f p2, Vector2f p3, float t)
5985//       {
5986//           float r = 1f - t;
5987//           float f0 = r * r * r;
5988//           float f1 = r * r * t * 3;
5989//           float f2 = r * t * t * 3;
5990//           float f3 = t * t * t;
5991//           return f0*p0 + f1*p1 + f2*p2 + f3*p3;
5992//       }
5993        
5994        
5995//      long memorySize = ((com.sun.management.OperatingSystemMXBean) ManagementFactory
5996//              .getOperatingSystemMXBean()).getTotalPhysicalMemorySize();
5997        protected static Boolean canCheckVramNVIDIA = null;
5998        public static boolean canCheckVram() {
5999                if (canCheckVramNVIDIA == null) {
6000                        String str = GL11.glGetString(GL11.GL_EXTENSIONS);
6001                        if (str != null) {
6002                                List<String> extensions = Arrays.asList(str.split(" "));
6003                                canCheckVramNVIDIA = extensions.contains("GL_NVX_gpu_memory_info");
6004                                //canCheckVramATI = extensions.contains("GL_ATI_meminfo");
6005                        } else {
6006                                canCheckVramNVIDIA = false;
6007                        }
6008                }
6009                return true;
6010        }
6011        /**
6012         * Reminder: call this on startup to see what the max is.
6013         * @return
6014         */
6015        public static int getVramFreeKB() {
6016                if (canCheckVramNVIDIA) {
6017                        return GL11.glGetInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX);
6018                } else {
6019                        return GL11.glGetInteger(ATIMeminfo.GL_TEXTURE_FREE_MEMORY_ATI);
6020                }
6021        }
6022        
6023        public static int getVramMaximumKB() {
6024                return GL11.glGetInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX);
6025        }
6026        public static int getVramDedicatedKB() {
6027                return GL11.glGetInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX);
6028        }
6029        public static int getVramUsedKB() {
6030                return getVramMaximumKB() - getVramFreeKB();
6031        }
6032
6033//      public static void printExtensions() {
6034//              String str = GL11.glGetString(GL11.GL_EXTENSIONS);
6035//              System.out.println(str);
6036//      }
6037        
6038        
6039        public static float IMPACT_VOLUME_MULT = Global.getSettings().getFloat("impactSoundVolumeMult");
6040        
6041        public static void playSound(ApplyDamageResultAPI result, Vector2f loc, Vector2f vel,
6042                                                                String lightShields, String solidShields, String heavyShields,
6043                                                                String lightHull, String solidHull, String heavyHull
6044                                                                ) {
6045                float shieldDam = result.getDamageToShields();
6046                float armorDam = result.getTotalDamageToArmor();
6047                float hullDam = result.getDamageToHull();
6048                float fluxDam = result.getEmpDamage();
6049                
6050                
6051                float totalDam = shieldDam + armorDam + hullDam;
6052                // if no damage, don't play sounds
6053                if (totalDam + fluxDam <= 0) return;
6054                
6055                float vol = 1f;
6056                
6057                float volMult = IMPACT_VOLUME_MULT;
6058                
6059                // if shields were damaged, then ONLY shields were damaged
6060                if (shieldDam > 0) {
6061                        String soundId = null;
6062                        if (shieldDam < 70) {
6063                                vol = shieldDam / 20f;
6064                                if (vol > 1) vol = 1;
6065                                soundId = lightShields;
6066                        } else if (shieldDam < 200) {
6067                                soundId = solidShields;
6068                        } else {
6069                                soundId = heavyShields;
6070                        }
6071                        if (soundId != null) {
6072                                Global.getSoundPlayer().playSound(soundId, 1f, vol * volMult, loc, vel);
6073                        }
6074                        return;
6075                }
6076                
6077                String soundId = null;
6078                
6079                float physicalDam = armorDam + hullDam + fluxDam;
6080                //System.out.println(physicalDam);
6081                if (physicalDam < 5) {
6082                        vol = physicalDam / 5f;
6083                        if (vol > 1) vol = 1;
6084                        soundId = lightHull;
6085                } else if (physicalDam < 40) {
6086                        soundId = lightHull;
6087                } else if (physicalDam < 150) {
6088                        soundId = solidHull;
6089                } else {
6090                        soundId = heavyHull;
6091                }
6092
6093                if (soundId != null) {
6094                        Global.getSoundPlayer().playSound(soundId, 1f, vol * volMult, loc, vel);
6095                }
6096                return;
6097        }
6098        
6099        
6100        public static float getShipWeight(ShipAPI ship) {
6101                return getShipWeight(ship, true);
6102        }
6103        
6104        public static float getShipWeight(ShipAPI ship, boolean adjustForNonCombat) {
6105                if (ship.isDrone()) return 0.1f;
6106                boolean nonCombat = ship.isNonCombat(false);
6107                float weight = 0;
6108                switch (ship.getHullSize()) {
6109                case CAPITAL_SHIP: weight += 8; break;
6110                case CRUISER: weight += 4; break;
6111                case DESTROYER: weight += 2; break;
6112                case FRIGATE: weight += 1; break;
6113                case FIGHTER: weight += 1; break;
6114                }
6115                if (ship.getHullSpec().isPhase() && (ship.isFrigate() || ship.isDestroyer())) {
6116                        weight += 2f;
6117                }
6118                if (nonCombat && adjustForNonCombat) weight *= 0.25f;
6119                if (ship.isDrone()) weight *= 0.1f;
6120                return weight;
6121        }
6122        
6123        public static float getIncapacitatedTime(ShipAPI ship) {
6124                float incapTime = 0f;
6125                if (ship.getFluxTracker().isVenting()){
6126                        incapTime = ship.getFluxTracker().getTimeToVent();
6127                } else if (ship.getFluxTracker().isOverloaded()) {
6128                        incapTime = ship.getFluxTracker().getOverloadTimeRemaining();
6129                }
6130                return incapTime;
6131        }
6132        
6133        public static boolean isAvoidingPlayerHalfheartedly(CampaignFleetAPI fleet) {
6134                if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_NEVER_AVOID_PLAYER_SLOWLY)) {
6135                        return false;
6136                }
6137                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
6138                boolean avoidingPlayer = fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_AVOID_PLAYER_SLOWLY);
6139                if (avoidingPlayer && !fleet.isHostileTo(player)) return true;
6140                
6141                CampaignFleetAPI fleeingFrom = fleet.getMemoryWithoutUpdate().getFleet(FleetAIFlags.NEAREST_FLEEING_FROM);
6142                if (fleeingFrom != null && fleeingFrom.isPlayerFleet()) {
6143                        if (Misc.shouldNotWantRunFromPlayerEvenIfWeaker(fleet) && fleet.isHostileTo(player)) {
6144                                return true;
6145                        }
6146                }
6147                return false;
6148        }
6149        
6150        /**
6151         * In vanilla, pirates and Luddic Path.
6152         * @param faction
6153         * @return
6154         */
6155        public static boolean isPirateFaction(FactionAPI faction) {
6156                return faction != null &&
6157                           faction.getCustomBoolean(Factions.CUSTOM_PIRATE_BEHAVIOR);// &&
6158                                        //faction.getCustomBoolean(Factions.CUSTOM_MAKES_PIRATE_BASES);
6159        }
6160        
6161        /**
6162         * Probably wrong sometimes...
6163         * @return "a" or "an" for word.
6164         */
6165        public static String getAOrAnFor(String word) {
6166                word = word.toLowerCase();
6167            for (String other : new String[] {"euler", "heir", "honest", "hono"}) {
6168                if (word.startsWith(other)) {
6169                    return "an";
6170                }
6171            }
6172
6173            if (word.startsWith("hour") && !word.startsWith("houri")) {
6174                return "an";
6175            }
6176
6177            Pattern p;
6178            Matcher m;
6179
6180            for (String regex : new String[] { "^e[uw]", "^onc?e\b", "^uni([^nmd]|mo)", "^u[bcfhjkqrst][aeiou]"}) {
6181                p = Pattern.compile("(?is)" + regex + ".*");
6182                    m = p.matcher(word);
6183                    if (m.matches()) {
6184                    return "a";
6185                    }
6186            }
6187
6188            p = Pattern.compile("(?is)" + "^U[NK][AIEO]");
6189            m = p.matcher(word);
6190            if (m.matches()) {
6191                return "a";
6192            }
6193
6194            for (String letter : new String[] { "a", "e", "i", "o", "u" }) {
6195                if (word.startsWith(letter)) {
6196                        return "an";
6197                }
6198            }
6199            
6200            p = Pattern.compile("(?is)" + "^y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)" + ".*");
6201            m = p.matcher(word);
6202            if (m.matches()) {
6203                return "an";
6204            }
6205
6206            return "a";
6207        }
6208        
6209        
6210        public static void moveToMarket(PersonAPI person, MarketAPI destination, boolean alwaysAddToCommDirectory) {
6211                ContactIntel intel = ContactIntel.getContactIntel(person);
6212                if (intel != null) {
6213                        intel.relocateToMarket(destination, false);
6214                } else {
6215                        boolean addToComms = alwaysAddToCommDirectory;
6216                        boolean hidden = false;
6217                        if (person.getMarket() != null) {
6218                                MarketAPI market = person.getMarket();
6219                                CommDirectoryEntryAPI entry = market.getCommDirectory().getEntryForPerson(person);
6220                                if (entry != null) {
6221                                        addToComms = true;
6222                                        hidden = entry.isHidden();
6223                                }
6224                                market.removePerson(person);
6225                                market.getCommDirectory().removePerson(person);
6226                        }
6227                        
6228                        if (!destination.getPeopleCopy().contains(person)) {
6229                                destination.addPerson(person);
6230                        }
6231                        person.setMarket(destination);
6232                        
6233                        if (addToComms) {
6234                                if (destination.getCommDirectory() != null && 
6235                                                destination.getCommDirectory().getEntryForPerson(person) == null) {
6236                                        destination.getCommDirectory().addPerson(person);
6237                                        if (hidden) {
6238                                                CommDirectoryEntryAPI entry = destination.getCommDirectory().getEntryForPerson(person);
6239                                                if (entry != null) {
6240                                                        entry.setHidden(true);
6241                                                }
6242                                        }
6243                                }
6244                        }
6245                }
6246        }
6247
6248        public static void makeStoryCritical(String marketId, String reason) {
6249                makeStoryCritical(Global.getSector().getEconomy().getMarket(marketId), reason);
6250        }
6251        public static void makeStoryCritical(MarketAPI market, String reason) {
6252                makeStoryCritical(market.getMemoryWithoutUpdate(), reason);
6253        }
6254        public static void makeStoryCritical(MemoryAPI memory, String reason) {
6255                setFlagWithReason(memory, MemFlags.STORY_CRITICAL, reason, true, -1f);
6256        }
6257        public static void makeNonStoryCritical(MarketAPI market, String reason) {
6258                makeNonStoryCritical(market.getMemoryWithoutUpdate(), reason);
6259        }
6260        public static void makeNonStoryCritical(MemoryAPI memory, String reason) {
6261                setFlagWithReason(memory, MemFlags.STORY_CRITICAL, reason, false, -1f);
6262        }
6263        public static boolean isStoryCritical(MarketAPI market) {
6264                return isStoryCritical(market.getMemoryWithoutUpdate());
6265        }
6266        public static boolean isStoryCritical(MemoryAPI memory) {
6267                return memory.getBoolean(MemFlags.STORY_CRITICAL);
6268        }
6269        
6270        
6271        /**
6272         * Whether it prevents salvage, surveying, etc. But NOT things that require only being
6273         * seen to ruin them, such as SpySat deployments.
6274         * @param fleet
6275         * @return
6276         */
6277        public static boolean isInsignificant(CampaignFleetAPI fleet) {
6278                boolean recentlyBeaten = fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_RECENTLY_DEFEATED_BY_PLAYER);
6279                if (recentlyBeaten) return true;
6280                
6281                CampaignFleetAPI pf = Global.getSector().getPlayerFleet();
6282                if (pf == null) return true; // ??
6283                if (fleet.getAI() != null) {
6284                        EncounterOption opt = fleet.getAI().pickEncounterOption(null, pf);
6285                        if (opt == EncounterOption.DISENGAGE || opt == EncounterOption.HOLD_VS_STRONGER) {
6286                                return true;
6287                        }
6288                        if (opt == EncounterOption.ENGAGE) {
6289                                return false;
6290                        }
6291                }
6292                int pfCount = pf.getFleetSizeCount();
6293                int otherCount = fleet.getFleetSizeCount();
6294                
6295                return otherCount <= pfCount / 4;
6296        }
6297        
6298        /**
6299         * Mainly for avoiding stuff like "pirate fleet with 4 rustbuckets will run away from the player's
6300         * 4 regular-quality frigates". Fleets that this evaluates to true for will avoid the player slowly.
6301         * @param fleet
6302         * @return
6303         */
6304        public static boolean shouldNotWantRunFromPlayerEvenIfWeaker(CampaignFleetAPI fleet) {
6305                boolean recentlyBeaten = fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_RECENTLY_DEFEATED_BY_PLAYER);
6306                if (recentlyBeaten) return true;
6307                if (fleet.getFleetData() == null) return false;
6308                
6309                float count = 0;
6310                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
6311                        if (!member.isCivilian() && member.getHullSpec() != null) {
6312                                switch (member.getHullSpec().getHullSize()) {
6313                                case CAPITAL_SHIP: count += 4; break;
6314                                case CRUISER: count += 3; break;
6315                                case DESTROYER: count += 2; break;
6316                                case FRIGATE: count += 1; break;
6317                                }
6318                        }
6319                }
6320                
6321                CampaignFleetAPI pf = Global.getSector().getPlayerFleet();
6322                float pfCount = 0;
6323                for (FleetMemberAPI member : pf.getFleetData().getMembersListCopy()) {
6324                        if (!member.isCivilian() && member.getHullSpec() != null) {
6325                                switch (member.getHullSpec().getHullSize()) {
6326                                case CAPITAL_SHIP: pfCount += 4; break;
6327                                case CRUISER: pfCount += 3; break;
6328                                case DESTROYER: pfCount += 2; break;
6329                                case FRIGATE: pfCount += 1; break;
6330                                }
6331                        }
6332                }
6333                
6334                if (count > pfCount * 0.67f) {
6335                        return true;
6336                }
6337                
6338                if (isInsignificant(fleet) && count <= 6) return true;
6339                
6340                return false;
6341        }
6342        
6343        public static float findKth(float[] arr, int k) {
6344                if (arr == null || arr.length <= k || k < 0) {
6345                        return -1;
6346                }
6347
6348                int from = 0;
6349                int to = arr.length - 1;
6350
6351                while (from < to) {
6352                        int r = from;
6353                        int w = to;
6354                        float mid = arr[(r + w) / 2];
6355
6356                        while (r < w) {
6357                                if (arr[r] >= mid) {
6358                                        float tmp = arr[w];
6359                                        arr[w] = arr[r];
6360                                        arr[r] = tmp;
6361                                        w--;
6362                                } else {
6363                                        r++;
6364                                }
6365                        }
6366
6367                        if (arr[r] > mid) r--;
6368
6369                        if (k <= r) {
6370                                to = r;
6371                        } else {
6372                                from = r + 1;
6373                        }
6374                }
6375
6376                return arr[k];
6377        }
6378
6379        public static float getAdjustedBaseRange(float base, ShipAPI ship, WeaponAPI weapon) {
6380                if (ship == null || weapon == null) return base;
6381                float flat = CombatListenerUtil.getWeaponBaseRangeFlatMod(ship, weapon);
6382                float percent = CombatListenerUtil.getWeaponBaseRangePercentMod(ship, weapon);
6383                float mult = CombatListenerUtil.getWeaponBaseRangeMultMod(ship, weapon);
6384                return (base * (1f + percent/100f) + flat) * mult;
6385        }
6386        
6387        
6388        public static Vector2f bezier(Vector2f p0, Vector2f p1, Vector2f p2, float t) {
6389                if (t < 0) t = 0;
6390                if (t > 1) t = 1;
6391                Vector2f r = new Vector2f();
6392                r.x = (1f - t) * (1f - t) * p0.x + 2f * (1f - t) * t * p1.x + t * t * p2.x;
6393                r.y = (1f - t) * (1f - t) * p0.y + 2f * (1f - t) * t * p1.y + t * t * p2.y;
6394                return r;
6395        }
6396        
6397        public static Vector2f bezierCubic(Vector2f p0, Vector2f p1, Vector2f p2, Vector2f p3, float t) {
6398                if (t < 0) t = 0;
6399                if (t > 1) t = 1;
6400                Vector2f r = new Vector2f();
6401                
6402                r.x = (1f - t) * (1f - t) * (1f -t) * p0.x + 
6403                                3f * (1f - t) * (1f - t) * t * p1.x +
6404                                3f * (1f - t) * t * t * p2.x +
6405                                t * t * t * p3.x;
6406                r.y = (1f - t) * (1f - t) * (1f -t) * p0.y + 
6407                                3f * (1f - t) * (1f - t) * t * p1.y +
6408                                3f * (1f - t) * t * t * p2.y +
6409                                t * t * t * p3.y;
6410                return r;
6411        }
6412        
6413        public static boolean isInsideSlipstream(Vector2f loc, float radius) {
6414                return isInsideSlipstream(loc, radius, Global.getSector().getHyperspace());
6415        }
6416        public static boolean isInsideSlipstream(Vector2f loc, float radius, LocationAPI location) {
6417                if (location == null) return false;
6418                for (CampaignTerrainAPI ter : location.getTerrainCopy()) {
6419                        if (ter.getPlugin() instanceof SlipstreamTerrainPlugin2) {
6420                                SlipstreamTerrainPlugin2 plugin = (SlipstreamTerrainPlugin2) ter.getPlugin();
6421                                if (plugin.containsPoint(loc, radius)) {
6422                                        return true;
6423                                }
6424                        }
6425                }
6426                return false;
6427        }
6428        public static boolean isInsideSlipstream(SectorEntityToken entity) {
6429                if (entity == null || entity.getContainingLocation() == null) return false;
6430                for (CampaignTerrainAPI ter : entity.getContainingLocation().getTerrainCopy()) {
6431                        if (ter.getPlugin() instanceof SlipstreamTerrainPlugin2) {
6432                                SlipstreamTerrainPlugin2 plugin = (SlipstreamTerrainPlugin2) ter.getPlugin();
6433                                if (plugin.containsEntity(entity)) {
6434                                        return true;
6435                                }
6436                        }
6437                }
6438                return false;
6439        }
6440        
6441        public static boolean isOutsideSector(Vector2f loc) {
6442                float sw = Global.getSettings().getFloat("sectorWidth");
6443                float sh = Global.getSettings().getFloat("sectorHeight");
6444                return loc.x < -sw/2f || loc.x > sw/2f || loc.y < -sh/2f || loc.y > sh/2f;
6445        }
6446        
6447        public static boolean crossesAnySlipstream(LocationAPI location, Vector2f from, Vector2f to) {
6448                for (CampaignTerrainAPI ter : location.getTerrainCopy()) {
6449                        if (ter.getPlugin() instanceof SlipstreamTerrainPlugin2) {
6450                                SlipstreamTerrainPlugin2 plugin = (SlipstreamTerrainPlugin2) ter.getPlugin();
6451                                List<SlipstreamSegment> segments = plugin.getSegments();
6452                                int skip = Math.max(20, segments.size() / 10);
6453                                for (int i = 0; i < segments.size(); i += skip) {
6454                                        int i2 = i + skip;
6455                                        if (i2 > segments.size() - skip/2) i2 = segments.size() - 1;
6456                                        if (i2 >= segments.size()) i2 = segments.size() - 1;
6457                                        
6458                                        if (i2 <= i) break;
6459                                        
6460                                        Vector2f p = intersectSegments(segments.get(i).loc, segments.get(i2).loc, from, to);
6461                                        if (p != null) return true;
6462                                }
6463                        }
6464                }
6465                return false;
6466        }
6467        
6468        public static void computeCoreWorldsExtent() {
6469                Vector2f min = new Vector2f();
6470                Vector2f max = new Vector2f();
6471                for (StarSystemAPI curr : Global.getSector().getStarSystems()) {
6472                        if (curr.hasTag(Tags.THEME_CORE)) {
6473                                Vector2f loc = curr.getLocation();
6474                                min.x = Math.min(min.x, loc.x);
6475                                min.y = Math.min(min.y, loc.y);
6476                                max.x = Math.max(max.x, loc.x);
6477                                max.y = Math.max(max.y, loc.y);
6478                        }
6479                }
6480                
6481                Vector2f core = Vector2f.add(min, max, new Vector2f());
6482                core.scale(0.5f);
6483                
6484                Global.getSector().getMemoryWithoutUpdate().set("$coreWorldsMin", min);
6485                Global.getSector().getMemoryWithoutUpdate().set("$coreWorldsMax", max);
6486                Global.getSector().getMemoryWithoutUpdate().set("$coreWorldsCenter", core);
6487        }
6488
6489        public static Vector2f getCoreMin() {
6490                Vector2f v = (Vector2f) Global.getSector().getMemoryWithoutUpdate().get("$coreWorldsMin");
6491                if (v == null) {
6492                        computeCoreWorldsExtent();
6493                        v = (Vector2f) Global.getSector().getMemoryWithoutUpdate().get("$coreWorldsMin");
6494                }
6495                return v;
6496        }
6497        public static Vector2f getCoreMax() {
6498                Vector2f v = (Vector2f) Global.getSector().getMemoryWithoutUpdate().get("$coreWorldsMax");
6499                if (v == null) {
6500                        computeCoreWorldsExtent();
6501                        v = (Vector2f) Global.getSector().getMemoryWithoutUpdate().get("$coreWorldsMax");
6502                }
6503                return v;
6504        }
6505        public static Vector2f getCoreCenter() {
6506                Vector2f v = (Vector2f) Global.getSector().getMemoryWithoutUpdate().get("$coreWorldsCenter");
6507                if (v == null) {
6508                        computeCoreWorldsExtent();
6509                        v = (Vector2f) Global.getSector().getMemoryWithoutUpdate().get("$coreWorldsCenter");
6510                }
6511                return v;
6512        }
6513        
6514        
6515//      public static void createColonyStatic(MarketAPI market) 
6516//      {
6517//              String factionId = Factions.PLAYER;
6518//              
6519//              market.setSize(3);
6520//              market.addCondition("population_3");
6521//              market.setFactionId(factionId);
6522//              market.setPlanetConditionMarketOnly(false);
6523//              
6524//              if (market.hasCondition(Conditions.DECIVILIZED))
6525//              {
6526//                      market.removeCondition(Conditions.DECIVILIZED);
6527//                      market.addCondition(Conditions.DECIVILIZED_SUBPOP);
6528//              }
6529//              market.addIndustry(Industries.POPULATION);
6530//              
6531//              market.addSubmarket(Submarkets.LOCAL_RESOURCES);
6532//              market.addSubmarket(Submarkets.SUBMARKET_STORAGE);
6533//              
6534//              market.setSurveyLevel(MarketAPI.SurveyLevel.FULL);
6535//              for (MarketConditionAPI cond : market.getConditions())
6536//              {
6537//                      cond.setSurveyed(true);
6538//              }
6539//              
6540//              Global.getSector().getEconomy().addMarket(market, true);
6541//              market.getPrimaryEntity().setFaction(factionId);
6542//              
6543//              market.setPlayerOwned(true);
6544//              market.addIndustry(Industries.SPACEPORT);
6545//              SubmarketAPI storage = market.getSubmarket(Submarkets.SUBMARKET_STORAGE);
6546//              if (storage != null)
6547//                      ((StoragePlugin)storage.getPlugin()).setPlayerPaidToUnlock(true);
6548//      }
6549        
6550
6551        public static boolean turnTowardsPointV2(MissileAPI missile, Vector2f point, float angVel) {
6552                float desiredFacing = getAngleInDegrees(missile.getLocation(), point);
6553                return turnTowardsFacingV2(missile, desiredFacing, angVel);
6554        }
6555        
6556        public static boolean turnTowardsFacingV2(MissileAPI missile, float desiredFacing, float relativeAngVel) {              
6557                
6558                float turnVel = missile.getAngularVelocity() - relativeAngVel;
6559                float absTurnVel = Math.abs(turnVel);
6560                
6561                float turnDecel = missile.getEngineController().getTurnDeceleration();
6562                // v t - 0.5 a t t = dist
6563                // dv = a t;  t = v / a
6564                float decelTime = absTurnVel / turnDecel; 
6565                float decelDistance = absTurnVel * decelTime - 0.5f * turnDecel * decelTime * decelTime;
6566                
6567                float facingAfterNaturalDecel = missile.getFacing() + Math.signum(turnVel) * decelDistance;
6568                float diffWithEventualFacing = getAngleDiff(facingAfterNaturalDecel, desiredFacing);
6569                float diffWithCurrFacing = getAngleDiff(missile.getFacing(), desiredFacing);
6570                
6571                if (diffWithEventualFacing > 1f) {
6572                        float turnDir = getClosestTurnDirection(missile.getFacing(), desiredFacing);
6573                        if (Math.signum(turnVel) == Math.signum(turnDir)) {
6574                                if (decelDistance > diffWithCurrFacing) {
6575                                        turnDir = -turnDir;
6576                                }
6577                        }
6578                        if (turnDir < 0) {
6579                                missile.giveCommand(ShipCommand.TURN_RIGHT);
6580                        } else if (turnDir >= 0) {
6581                                missile.giveCommand(ShipCommand.TURN_LEFT);
6582                        } else {
6583                                return false;
6584                        }
6585                }
6586                return false;
6587        }
6588        
6589        public static boolean turnTowardsFacingV2(ShipAPI ship, float desiredFacing, float relativeAngVel) {            
6590                
6591                float turnVel = ship.getAngularVelocity() - relativeAngVel;
6592                float absTurnVel = Math.abs(turnVel);
6593                
6594                float turnDecel = ship.getEngineController().getTurnDeceleration();
6595                // v t - 0.5 a t t = dist
6596                // dv = a t;  t = v / a
6597                float decelTime = absTurnVel / turnDecel; 
6598                float decelDistance = absTurnVel * decelTime - 0.5f * turnDecel * decelTime * decelTime;
6599                
6600                float facingAfterNaturalDecel = ship.getFacing() + Math.signum(turnVel) * decelDistance;
6601                float diffWithEventualFacing = getAngleDiff(facingAfterNaturalDecel, desiredFacing);
6602                float diffWithCurrFacing = getAngleDiff(ship.getFacing(), desiredFacing);
6603                
6604                if (diffWithEventualFacing > 1f) {
6605                        float turnDir = getClosestTurnDirection(ship.getFacing(), desiredFacing);
6606                        if (Math.signum(turnVel) == Math.signum(turnDir)) {
6607                                if (decelDistance > diffWithCurrFacing) {
6608                                        turnDir = -turnDir;
6609                                }
6610                        }
6611                        if (turnDir < 0) {
6612                                ship.giveCommand(ShipCommand.TURN_RIGHT, null, 0);
6613                        } else if (turnDir >= 0) {
6614                                ship.giveCommand(ShipCommand.TURN_LEFT, null, 0);
6615                        } else {
6616                                return false;
6617                        }
6618                }
6619                return false;
6620        }
6621        
6622        public static int getUntrustwortyCount() {
6623                int count = Global.getSector().getPlayerMemoryWithoutUpdate().getInt(MemFlags.PLAYER_UNTRUSTWORTHY);
6624                return count;
6625        }
6626        
6627        public static void incrUntrustwortyCount() {
6628                int count = getUntrustwortyCount();
6629                Global.getSector().getPlayerMemoryWithoutUpdate().set(MemFlags.PLAYER_UNTRUSTWORTHY, count + 1);
6630        }
6631        
6632        public static ReputationAdjustmentResult adjustRep(PersonAPI person, float delta, TextPanelAPI text) {
6633                return adjustRep(person, delta, null, text);
6634        }
6635        public static ReputationAdjustmentResult adjustRep(PersonAPI person, float delta, RepLevel limit, TextPanelAPI text) {
6636                return adjustRep(person, delta, limit, text, true, true);
6637        }
6638        public static ReputationAdjustmentResult adjustRep(PersonAPI person, float delta, RepLevel limit, TextPanelAPI text,
6639                        boolean addMessageOnNoChange, boolean withMessage) {
6640                CustomRepImpact impact = new CustomRepImpact();
6641                impact.delta = delta;
6642                if (limit != null) {
6643                        impact.limit = limit;
6644                }
6645                return Global.getSector().adjustPlayerReputation(
6646                                new RepActionEnvelope(RepActions.CUSTOM, 
6647                                                impact, null, text, addMessageOnNoChange, withMessage),
6648                                                person);
6649        }
6650        
6651        public static ReputationAdjustmentResult adjustRep(String factionId, float delta, TextPanelAPI text) {
6652                return adjustRep(factionId, delta, null, text);
6653        }
6654        public static ReputationAdjustmentResult adjustRep(String factionId, float delta, RepLevel limit, TextPanelAPI text) {
6655                return adjustRep(factionId, delta, limit, text, true, true);
6656        }
6657        public static ReputationAdjustmentResult adjustRep(String factionId, float delta, RepLevel limit, TextPanelAPI text,
6658                        boolean addMessageOnNoChange, boolean withMessage) {
6659                CustomRepImpact impact = new CustomRepImpact();
6660                impact.delta = delta;
6661                if (limit != null) {
6662                        impact.limit = limit;
6663                }
6664                return Global.getSector().adjustPlayerReputation(
6665                                new RepActionEnvelope(RepActions.CUSTOM, 
6666                                                impact, null, text, addMessageOnNoChange, withMessage),
6667                                factionId);
6668        }
6669        
6670        public static String getHullSizeStr(HullSize size) {
6671                switch (size) {
6672                case CAPITAL_SHIP: return "Capital";
6673                case CRUISER: return "Cruiser";
6674                case DESTROYER: return "Destroyer";
6675                case FIGHTER: return "Fighter";
6676                case FRIGATE: return "Frigate";
6677                }
6678                return "Unknown";
6679        }
6680        
6681        public static float getColorDist(Color one, Color two) {
6682                float r = Math.abs(one.getRed() - two.getRed());
6683                float g = Math.abs(one.getGreen() - two.getGreen());
6684                float b = Math.abs(one.getBlue() - two.getBlue());
6685                float a = Math.abs(one.getAlpha() - two.getAlpha());
6686                
6687                return (float) Math.sqrt(r * r + g * g + b * b + a * a);
6688        }
6689        
6690        
6691        public static float FRINGE_THRESHOLD = 0.7f;
6692        
6693        public static boolean isFringe(SectorEntityToken entity) {
6694                return isFringe(entity.getLocationInHyperspace());
6695        }
6696        public static boolean isFringe(StarSystemAPI system) {
6697                return isFringe(system.getLocation());
6698        }
6699        public static boolean isFringe(Vector2f loc) {
6700                return getFringeFactor(loc) > FRINGE_THRESHOLD;
6701        }
6702        public static float getFringeFactor(Vector2f loc) {
6703                float sw = Global.getSettings().getFloat("sectorWidth");
6704                float sh = Global.getSettings().getFloat("sectorHeight");
6705                float mult = 0.8f;
6706                //float mult = 1f;
6707                float a = sw * 0.5f * mult;
6708                float b = sh * 0.5f * mult;
6709                float x = loc.x;
6710                float y = loc.y;
6711                
6712                float f = (x * x) / (a * a) + (y * y)/ (b * b);
6713                if (f < 0) f = 0;
6714                if (f > 1) f = 1;
6715                return f;
6716        }
6717        
6718        public static boolean isHiddenBase(MarketAPI market) {
6719                return market.getMemoryWithoutUpdate().getBoolean(MemFlags.HIDDEN_BASE_MEM_FLAG);
6720        }
6721        
6722        
6723        public static boolean isReversePolarity(SectorEntityToken entity) {
6724                return entity.getMemoryWithoutUpdate().getBoolean(ReversePolarityToggle.REVERSED_POLARITY);
6725        }
6726        
6727        
6728        
6729        public static enum CatalogEntryType {
6730                PLANET("P"),
6731                GIANT("G"),
6732                STAR("S"),
6733                BLACK_HOLE("B");
6734                
6735                public String suffix;
6736                private CatalogEntryType(String suffix) {
6737                        this.suffix = suffix;
6738                }
6739                
6740        }
6741        public static String genEntityCatalogId(CatalogEntryType type) {
6742                return genEntityCatalogId(-1, -1, -1, type);
6743        }
6744        public static String genEntityCatalogId(int cycleOverride, int monthOverride, int dayOverride, CatalogEntryType type) {
6745                return genEntityCatalogId(null, cycleOverride, monthOverride, dayOverride, type);
6746        }
6747        public static String genEntityCatalogId(String firstChar, int cycleOverride, int monthOverride, int dayOverride, CatalogEntryType type) {
6748                
6749                String base = "Perseus";
6750                
6751                int cycle = Global.getSector().getClock().getCycle();
6752                cycle += 3000;
6753                if (cycleOverride > 0) cycle = cycleOverride; 
6754                
6755                int month = Global.getSector().getClock().getMonth();
6756                if (monthOverride > 0) month = monthOverride;
6757                int day = Global.getSector().getClock().getDay();
6758                if (dayOverride > 0) day = dayOverride;
6759                
6760                String s1 = Integer.toHexString(cycle).toUpperCase();
6761                
6762                Random r = StarSystemGenerator.random;
6763                
6764                String s0 = Integer.toHexString(r.nextInt(16)).toUpperCase();
6765                if (firstChar != null) s0 = firstChar;
6766                
6767                String s2 = Integer.toHexString(month).toUpperCase();
6768                String s3 = Integer.toHexString(day).toUpperCase();
6769                
6770//              s1 = "" + cycle;
6771//              s0 = "";
6772                
6773                while (s1.length() < 3) s1 = "0" + s1;
6774                while (s3.length() < 2) s3 = "0" + s3;
6775                
6776                return base + " " + s0 + s1 + "-" + s2 + s3 + type.suffix;
6777        }
6778        
6779        public static float getAveragePlanetRadius(PlanetSpecAPI spec) {
6780                if (spec.isStar()) {
6781                        StarGenDataSpec starData = (StarGenDataSpec) 
6782                                        Global.getSettings().getSpec(StarGenDataSpec.class, spec.getPlanetType(), true);
6783                        if (starData != null) { 
6784                                return (starData.getMinRadius() + starData.getMaxRadius()) * 0.5f;
6785                        }
6786                }
6787                
6788                PlanetGenDataSpec planetData = (PlanetGenDataSpec) 
6789                                Global.getSettings().getSpec(PlanetGenDataSpec.class, spec.getPlanetType(), true);
6790                if (planetData != null) {
6791                        return (planetData.getMinRadius() + planetData.getMaxRadius()) * 0.5f;
6792                }
6793                
6794                return 200f;
6795        }
6796        
6797        public static boolean canPlanetTypeRollHabitable(PlanetSpecAPI spec) {
6798                return canPlanetTypeRollCondition(spec, Conditions.HABITABLE);
6799        }
6800        
6801        public static boolean canPlanetTypeRollCondition(PlanetSpecAPI spec, String id) {
6802                ConditionGenDataSpec hab = (ConditionGenDataSpec) 
6803                                Global.getSettings().getSpec(ConditionGenDataSpec.class, id, true);
6804
6805                PlanetGenDataSpec genData = (PlanetGenDataSpec) 
6806                                Global.getSettings().getSpec(PlanetGenDataSpec.class, spec.getPlanetType(), true);
6807
6808                if (genData != null && hab != null) {
6809                        String planetCat = genData.getCategory();
6810                        if (hab.hasMultiplier(planetCat) && hab.getMultiplier(planetCat) > 0) {
6811                                return true;
6812                        }
6813                }
6814                return false;
6815        }
6816        
6817        public static int getMaxMarketSize(MarketAPI market) {
6818                return (int)Math.round(market.getStats().getDynamic().getMod(
6819                                                        Stats.MAX_MARKET_SIZE).computeEffective(Misc.MAX_COLONY_SIZE));
6820        }
6821        
6822        public static float countEnemyWeightInArc(ShipAPI ship, float dir, float arc, float maxRange, boolean ignoreFightersAndModules) {
6823                return countEnemyWeightInArcAroundLocation(ship, ship.getLocation(), dir, arc, maxRange, null, ignoreFightersAndModules);
6824        }
6825        public static float countEnemyWeightInArcAroundLocation(ShipAPI ship, Vector2f loc, float dir, float arc, float maxRange,
6826                                                                ShipAPI ignore, boolean ignoreFightersAndModules) {
6827                return countEnemyWeightInArcAroundLocation(ship.getOwner(), loc, dir, arc, maxRange, ignore, ignoreFightersAndModules, false);
6828        }
6829        public static float countEnemyWeightInArcAroundLocation(int owner, Vector2f loc, 
6830                                float dir, float arc, float maxRange,
6831                                ShipAPI ignore, boolean ignoreFightersAndModules, boolean awareOnly) {
6832                CombatEngineAPI engine = Global.getCombatEngine();
6833                List<ShipAPI> ships = engine.getAllShips();
6834                
6835                float weight = 0;
6836                for (ShipAPI other : ships) {
6837                        if (ignoreFightersAndModules) {
6838                                if (other.isFighter()) continue;
6839                                if (other.isStationModule()) continue;
6840                        }
6841                        if (other.isFighter() && other.getWing() != null && !other.getWing().isLeader(other)) continue;
6842                        if (other.isHulk()) continue;
6843                        if (other.isDrone()) continue;
6844                        if (other.isShuttlePod()) continue;
6845                        if (other.getOwner() == 100) continue;
6846                        if (owner == other.getOwner()) continue;
6847                        //if (other.isRetreating()) continue;
6848                        if (other.controlsLocked()) continue;
6849                        if (other == ignore) continue;
6850                        if (awareOnly && !engine.isAwareOf(owner, other)) continue;
6851                        
6852                        float dist = getDistance(loc, other.getLocation());
6853                        if (dist > maxRange) continue;
6854
6855                        if (arc >= 360f || isInArc(dir, arc, loc, other.getLocation())) {
6856                                weight += getShipWeight(other);
6857                                //weight += other.getHullSize().ordinal();
6858                        }
6859                }
6860                return weight;
6861        }
6862        
6863        public static float [] getFloatArray(String key) {
6864                try {
6865                        JSONArray arr = Global.getSettings().getJSONArray(key);
6866                        float [] result = new float [arr.length()];
6867                        for (int i = 0; i < arr.length(); i++) {
6868                                result[i] = (float) arr.optDouble(i, 0f);
6869                        }
6870                        return result;
6871                } catch (JSONException e) {
6872                        return null;
6873                }
6874        }
6875        
6876        public static enum WeaponSkinType {
6877                UNDER,
6878                TURRET,
6879                HARDPOINT,
6880                TURRET_GLOW,
6881                HARDPOINT_GLOW,
6882                TURRET_BARRELS,
6883                HARDPOINT_BARRELS,
6884        }
6885        
6886        public static SpriteAPI getWeaponSkin(ShipAPI ship, String weaponId, WeaponSkinType type) {
6887                String cat = null;
6888                SpriteAPI skin = null;
6889                if (ship.getOwner() == 0 || ship.getOriginalOwner() == 0) {
6890                        cat = "weaponSkinsPlayerOnly";
6891                        skin = getWeaponSkin(cat, weaponId, ship, type);
6892                }
6893                if (skin != null) return skin;
6894                
6895                cat = "weaponSkinsPlayerAndNPC";
6896                skin = getWeaponSkin(cat, weaponId, ship, type);
6897                return skin;
6898        }
6899        
6900        
6901        public static SpriteAPI getWeaponSkin(String cat, String weaponId, ShipAPI ship, WeaponSkinType type) {
6902                
6903                String exclude = "weaponSkinsExcludeFromSharing";
6904                String style = ship.getHullStyleId();
6905                
6906                List<String> skins = Global.getSettings().getSpriteKeys(cat);
6907                Set<String> noSharing = new LinkedHashSet<String>(Global.getSettings().getSpriteKeys(exclude));
6908                
6909                List<SpriteAPI> matching = new ArrayList<SpriteAPI>();
6910                String keyForHull = weaponId + ":" + style + ":" + type.name();
6911                for (String key : skins) {
6912                        if (key.equals(keyForHull)) {
6913                                return Global.getSettings().getSprite(cat, key);
6914                        }
6915                        if (key.startsWith(weaponId) && key.endsWith(type.name()) && !noSharing.contains(key)) {
6916                                matching.add(Global.getSettings().getSprite(cat, key));
6917                        }
6918                }
6919                
6920                if (!matching.isEmpty()) {
6921                        SpriteAPI best = null;
6922                        float minDist = Float.MAX_VALUE;
6923                        
6924                        for (SpriteAPI curr : matching) {
6925                                float dist = Misc.getColorDist(ship.getSpriteAPI().getAverageBrightColor(), curr.getAverageBrightColor());
6926                                if (dist < minDist) {
6927                                        best = curr;
6928                                        minDist = dist;
6929                                }
6930                        }
6931                        return best;
6932                }
6933                return null;
6934        }
6935}
6936
6937
6938
6939
6940
6941
6942
6943
6944
6945
6946
6947
6948
6949
6950
6951