001package com.fs.starfarer.api.impl.combat;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.List;
006import java.util.Random;
007
008import java.awt.Color;
009
010import org.lwjgl.util.vector.Vector2f;
011
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.campaign.BattleCreationPlugin;
014import com.fs.starfarer.api.campaign.CampaignFleetAPI;
015import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
016import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI;
017import com.fs.starfarer.api.campaign.LocationAPI;
018import com.fs.starfarer.api.campaign.PlanetAPI;
019import com.fs.starfarer.api.campaign.SectorEntityToken;
020import com.fs.starfarer.api.campaign.StarSystemAPI;
021import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
022import com.fs.starfarer.api.combat.BattleCreationContext;
023import com.fs.starfarer.api.combat.CombatEngineAPI;
024import com.fs.starfarer.api.combat.MissileAPI;
025import com.fs.starfarer.api.combat.ShipAPI;
026import com.fs.starfarer.api.fleet.FleetGoal;
027import com.fs.starfarer.api.fleet.FleetMemberAPI;
028import com.fs.starfarer.api.impl.campaign.ids.Planets;
029import com.fs.starfarer.api.impl.campaign.ids.Tags;
030import com.fs.starfarer.api.impl.campaign.ids.Terrain;
031import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceAbyssPluginImpl;
032import com.fs.starfarer.api.impl.campaign.terrain.PulsarBeamTerrainPlugin;
033import com.fs.starfarer.api.impl.campaign.terrain.StarCoronaTerrainPlugin;
034import com.fs.starfarer.api.input.InputEventAPI;
035import com.fs.starfarer.api.mission.FleetSide;
036import com.fs.starfarer.api.mission.MissionDefinitionAPI;
037import com.fs.starfarer.api.util.Misc;
038import com.fs.starfarer.api.util.WeightedRandomPicker;
039
040public class BattleCreationPluginImpl implements BattleCreationPlugin {
041
042        public interface NebulaTextureProvider {
043                String getNebulaTex();
044                String getNebulaMapTex();
045        }
046        
047        
048        public static float ABYSS_SHIP_SPEED_PENALTY = 20f;
049        public static float ABYSS_MISSILE_SPEED_PENALTY = 20f;
050        //public static float ABYSS_MISSILE_FLIGHT_TIME_MULT = 1.25f;
051        public static float ABYSS_OVERLAY_ALPHA = 0.2f;
052        
053        protected float width, height;
054        
055        protected float xPad = 2000;
056        protected float yPad = 2000;
057        
058        protected List<String> objs = null;
059
060        protected float prevXDir = 0;
061        protected float prevYDir = 0;
062        protected boolean escape;
063        
064        protected BattleCreationContext context;
065        protected MissionDefinitionAPI loader;
066        
067        public void initBattle(final BattleCreationContext context, MissionDefinitionAPI loader) {
068
069                this.context = context;
070                this.loader = loader;
071                CampaignFleetAPI playerFleet = context.getPlayerFleet();
072                CampaignFleetAPI otherFleet = context.getOtherFleet();
073                FleetGoal playerGoal = context.getPlayerGoal();
074                FleetGoal enemyGoal = context.getOtherGoal();
075
076                // doesn't work for consecutive engagements; haven't investigated why
077                //Random random = Misc.getRandom(Misc.getNameBasedSeed(otherFleet), 23);
078                
079                Random random = Misc.getRandom(Misc.getSalvageSeed(otherFleet) *
080                                (long)otherFleet.getFleetData().getNumMembers(), 23);
081                //System.out.println("RNG: " + random.nextLong());
082                //random = new Random(1213123L);
083                //Random random = Misc.random;
084                
085                escape = playerGoal == FleetGoal.ESCAPE || enemyGoal == FleetGoal.ESCAPE;
086                
087                int maxFP = (int) Global.getSettings().getFloat("maxNoObjectiveBattleSize");
088                int fpOne = 0;
089                int fpTwo = 0;
090                for (FleetMemberAPI member : playerFleet.getFleetData().getMembersListCopy()) {
091                        if (member.canBeDeployedForCombat() || playerGoal == FleetGoal.ESCAPE) {
092                                fpOne += member.getUnmodifiedDeploymentPointsCost();
093                        }
094                }
095                for (FleetMemberAPI member : otherFleet.getFleetData().getMembersListCopy()) {
096                        if (member.canBeDeployedForCombat() || playerGoal == FleetGoal.ESCAPE) {
097                                fpTwo += member.getUnmodifiedDeploymentPointsCost();
098                        }
099                }
100                
101                int smaller = Math.min(fpOne, fpTwo);
102                
103                boolean withObjectives = smaller > maxFP;
104                //withObjectives = true;
105                if (!context.objectivesAllowed) {
106                        withObjectives = false;
107                } else if (context.forceObjectivesOnMap) {
108                        withObjectives = true;
109                }
110                
111                int numObjectives = 0;
112                if (withObjectives) {
113//                      if (fpOne + fpTwo > maxFP + 70) {
114//                              numObjectives = 3 
115//                              numObjectives = 3 + (int)(Math.random() * 2.0);
116//                      } else {
117//                              numObjectives = 2 + (int)(Math.random() * 2.0);
118//                      }
119                        if (fpOne + fpTwo > maxFP + 70) {
120                                numObjectives = 4;
121                                //numObjectives = 3 + (int)(Math.random() * 2.0);
122                        } else {
123                                numObjectives = 3 + random.nextInt(2);
124                                //numObjectives = 2 + (int)(Math.random() * 2.0);
125                        }
126                }
127                
128                // shouldn't be possible, but..
129                if (numObjectives > 4) {
130                        numObjectives = 4;
131                }
132                
133                int baseCommandPoints = (int) Global.getSettings().getFloat("startingCommandPoints");
134
135                // 
136                loader.initFleet(FleetSide.PLAYER, "ISS", playerGoal, false,
137                                context.getPlayerCommandPoints() - baseCommandPoints,
138                                (int) playerFleet.getCommanderStats().getCommandPoints().getModifiedValue() - baseCommandPoints);
139                loader.initFleet(FleetSide.ENEMY, "", enemyGoal, true, 
140                                (int) otherFleet.getCommanderStats().getCommandPoints().getModifiedValue() - baseCommandPoints);
141
142                List<FleetMemberAPI> playerShips = playerFleet.getFleetData().getCombatReadyMembersListCopy();
143                if (playerGoal == FleetGoal.ESCAPE) {
144                        playerShips = playerFleet.getFleetData().getMembersListCopy();
145                }
146                for (FleetMemberAPI member : playerShips) {
147                        loader.addFleetMember(FleetSide.PLAYER, member);
148                }
149                
150                
151                List<FleetMemberAPI> enemyShips = otherFleet.getFleetData().getCombatReadyMembersListCopy();
152                if (enemyGoal == FleetGoal.ESCAPE) {
153                        enemyShips = otherFleet.getFleetData().getMembersListCopy();
154                }
155                for (FleetMemberAPI member : enemyShips) {
156                        loader.addFleetMember(FleetSide.ENEMY, member);
157                }
158                
159                width = 18000f;
160                height = 18000f;
161                
162                if (escape) {
163                        width = 18000f;
164                        //height = 24000f;
165                        height = 18000f;
166                } else if (withObjectives) {
167                        width = 24000f;
168                        if (numObjectives == 2) {
169                                height = 14000f;
170                        } else {
171                                height = 18000f;
172                        }
173                }
174                
175                createMap(random);
176                
177                context.setInitialDeploymentBurnDuration(1.5f);
178                context.setNormalDeploymentBurnDuration(6f);
179                context.setEscapeDeploymentBurnDuration(1.5f);
180                
181                xPad = 2000f;
182                yPad = 3000f;
183                
184                if (escape) {
185//                      addEscapeObjectives(loader, 4);
186//                      context.setInitialEscapeRange(7000f);
187//                      context.setFlankDeploymentDistance(9000f);
188                        addEscapeObjectives(loader, 2, random);
189//                      context.setInitialEscapeRange(4000f);
190//                      context.setFlankDeploymentDistance(8000f);
191
192                        context.setInitialEscapeRange(Global.getSettings().getFloat("escapeStartDistance"));
193                        context.setFlankDeploymentDistance(Global.getSettings().getFloat("escapeFlankDistance"));
194                        
195                        loader.addPlugin(new EscapeRevealPlugin(context));
196                } else {
197                        if (withObjectives) {
198                                addObjectives(loader, numObjectives, random);
199                                context.setStandoffRange(height - 4500f);
200                        } else {
201                                context.setStandoffRange(6000f);
202                        }
203                        
204                        context.setFlankDeploymentDistance(height/2f); // matters for Force Concentration
205                }
206        }
207        
208        public void afterDefinitionLoad(final CombatEngineAPI engine) {
209                if (coronaIntensity > 0 && (corona != null || pulsar != null)) {
210                        String name = "Corona";
211                        if (pulsar != null) name = pulsar.getTerrainName();
212                        else if (corona != null) name = corona.getTerrainName();
213                        
214                        final String name2 = name;
215                        
216//                      CombatFleetManagerAPI manager = engine.getFleetManager(FleetSide.PLAYER);
217//                      for (FleetMemberAPI member : manager.getReservesCopy()) {
218//                      }
219                        final Object key1 = new Object();
220                        final Object key2 = new Object();
221                        final String icon = Global.getSettings().getSpriteName("ui", "icon_tactical_cr_penalty");
222                        engine.addPlugin(new BaseEveryFrameCombatPlugin() {
223                                @Override
224                                public void advance(float amount, List<InputEventAPI> events) {
225                                        engine.maintainStatusForPlayerShip(key1, icon, name2, "reduced peak time", true);
226                                        engine.maintainStatusForPlayerShip(key2, icon, name2, "faster CR degradation", true);
227                                }
228                        });
229                }
230                
231                if (abyssalDepth > 0) {
232                        Color color = Misc.scaleColor(Color.white, 1f - abyssalDepth);
233                        engine.setBackgroundColor(color);
234
235                        color = Misc.scaleAlpha(Color.black, abyssalDepth * ABYSS_OVERLAY_ALPHA);
236                        engine.setBackgroundGlowColor(color);
237                        engine.setBackgroundGlowColorNonAdditive(true);
238                        
239                        if (abyssalDepth > HyperspaceAbyssPluginImpl.DEPTH_THRESHOLD_FOR_NO_DUST_PARTICLES_IN_COMBAT) {
240                                engine.setRenderStarfield(false);
241                        }
242                        
243                        final Object key1 = new Object();
244                        final Object key2 = new Object();
245                        final String icon = Global.getSettings().getSpriteName("ui", "icon_tactical_engine_damage");
246                        final String name = "Abyssal hyperspace";
247                        engine.addPlugin(new BaseEveryFrameCombatPlugin() {
248                                @Override
249                                public void advance(float amount, List<InputEventAPI> events) {
250                                        String percentSpeed = "-" + (int)Math.round(ABYSS_SHIP_SPEED_PENALTY) + "%";
251                                        String percentMissile = "-" + (int)Math.round(ABYSS_MISSILE_SPEED_PENALTY) + "%";
252                                        engine.maintainStatusForPlayerShip(key1, icon, name, percentSpeed + " top speed", true);
253                                        engine.maintainStatusForPlayerShip(key2, icon, name, percentMissile + " missle speed / range", true);
254                                        
255                                        String modId = "abyssal";
256                                        float modW = -0.0f * abyssalDepth;
257                                        float modL = -0.33f * abyssalDepth;
258                                        float modG = -0.5f * abyssalDepth;
259                                        
260                                        for (ShipAPI curr : engine.getShips()) {
261                                                if (curr.isHulk()) continue;
262                                                
263                                                curr.getEngineController().fadeToOtherColor(this, Color.black, null, 1f, abyssalDepth * 0.4f);
264                                                curr.getEngineController().extendFlame(this, modL, modW, modG);
265                                                
266                                                curr.getMutableStats().getMaxSpeed().modifyMult(modId, 
267                                                                                        1f - abyssalDepth * ABYSS_SHIP_SPEED_PENALTY * 0.01f);
268                                                curr.getMutableStats().getMissileWeaponRangeBonus().modifyMult(modId, 
269                                                                                        1f - abyssalDepth * ABYSS_MISSILE_SPEED_PENALTY * 0.01f);
270                                                curr.getMutableStats().getMissileMaxSpeedBonus().modifyMult(modId,
271                                                                                        1f - abyssalDepth * ABYSS_MISSILE_SPEED_PENALTY * 0.01f);
272                                        }
273                                        
274                                        for (MissileAPI missile : engine.getMissiles()) {
275                                                missile.getEngineController().fadeToOtherColor(this, Color.black, null, 1f, abyssalDepth * 0.4f);
276                                                missile.getEngineController().extendFlame(this, modL, modW, 0f);
277                                        }
278                                                
279                                }
280                        });
281                        
282                }
283        }
284        
285        
286        protected float abyssalDepth = 0f;
287        protected float coronaIntensity = 0f;
288        protected StarCoronaTerrainPlugin corona = null;
289        protected PulsarBeamTerrainPlugin pulsar = null;
290        protected void createMap(Random random) {
291                loader.initMap((float)-width/2f, (float)width/2f, (float)-height/2f, (float)height/2f);
292                
293                CampaignFleetAPI playerFleet = context.getPlayerFleet();
294                String nebulaTex = null;
295                String nebulaMapTex = null;
296                boolean inNebula = false;
297
298                boolean protectedFromCorona = false;
299                for (CustomCampaignEntityAPI curr : playerFleet.getContainingLocation().getCustomEntitiesWithTag(Tags.PROTECTS_FROM_CORONA_IN_BATTLE)) {
300                        if (Misc.getDistance(curr.getLocation(), playerFleet.getLocation()) <= curr.getRadius() + Global.getSector().getPlayerFleet().getRadius() + 10f) {
301                                protectedFromCorona = true;
302                                break;
303                        }
304                }
305                
306                abyssalDepth = Misc.getAbyssalDepth(playerFleet);
307                
308                float numRings = 0;
309                
310                Color coronaColor = null;
311                // this assumes that all nebula in a system are of the same color
312                for (CampaignTerrainAPI terrain : playerFleet.getContainingLocation().getTerrainCopy()) {
313                        //if (terrain.getType().equals(Terrain.NEBULA)) {
314                        if (terrain.getPlugin() instanceof NebulaTextureProvider) {
315                                if (terrain.getPlugin().containsEntity(playerFleet)) {
316                                        inNebula = true;
317                                        if (terrain.getPlugin() instanceof NebulaTextureProvider) {
318                                                NebulaTextureProvider provider = (NebulaTextureProvider) terrain.getPlugin();
319                                                nebulaTex = provider.getNebulaTex();
320                                                nebulaMapTex = provider.getNebulaMapTex();
321                                        }
322                                } else {
323                                        if (nebulaTex == null) {
324                                                if (terrain.getPlugin() instanceof NebulaTextureProvider) {
325                                                        NebulaTextureProvider provider = (NebulaTextureProvider) terrain.getPlugin();
326                                                        nebulaTex = provider.getNebulaTex();
327                                                        nebulaMapTex = provider.getNebulaMapTex();
328                                                }       
329                                        }
330                                }
331                        } else if (terrain.getPlugin() instanceof StarCoronaTerrainPlugin && pulsar == null && !protectedFromCorona) {
332                                StarCoronaTerrainPlugin plugin = (StarCoronaTerrainPlugin) terrain.getPlugin();
333                                if (plugin.containsEntity(playerFleet)) {
334                                        float angle = Misc.getAngleInDegrees(terrain.getLocation(), playerFleet.getLocation());
335                                        Color color = plugin.getAuroraColorForAngle(angle);
336                                        float intensity = plugin.getIntensityAtPoint(playerFleet.getLocation());
337                                        intensity = 0.4f + 0.6f * intensity;
338                                        int alpha = (int)(80f * intensity);
339                                        color = Misc.setAlpha(color, alpha);
340                                        if (coronaColor == null || coronaColor.getAlpha() < alpha) {
341                                                coronaColor = color;
342                                                coronaIntensity = intensity;
343                                                corona = plugin;
344                                        }
345                                }
346                        } else if (terrain.getPlugin() instanceof PulsarBeamTerrainPlugin && !protectedFromCorona) {
347                                PulsarBeamTerrainPlugin plugin = (PulsarBeamTerrainPlugin) terrain.getPlugin();
348                                if (plugin.containsEntity(playerFleet)) {
349                                        float angle = Misc.getAngleInDegreesStrict(terrain.getLocation(), playerFleet.getLocation());
350                                        Color color = plugin.getPulsarColorForAngle(angle);
351                                        float intensity = plugin.getIntensityAtPoint(playerFleet.getLocation());
352                                        intensity = 0.4f + 0.6f * intensity;
353                                        int alpha = (int)(80f * intensity);
354                                        color = Misc.setAlpha(color, alpha);
355                                        if (coronaColor == null || coronaColor.getAlpha() < alpha) {
356                                                coronaColor = color;
357                                                coronaIntensity = intensity;
358                                                pulsar = plugin;
359                                                corona = null;
360                                        }
361                                }
362                        } else if (terrain.getType().equals(Terrain.RING)) {
363                                if (terrain.getPlugin().containsEntity(playerFleet)) {
364                                        numRings++;
365                                }
366                        }
367                }
368                if (nebulaTex != null) {
369                        loader.setNebulaTex(nebulaTex);
370                        loader.setNebulaMapTex(nebulaMapTex);
371                }
372                
373                if (coronaColor != null) {
374                        loader.setBackgroundGlowColor(coronaColor);
375                }
376                
377                int numNebula = 15;
378                if (inNebula) {
379                        numNebula = 100;
380                }
381                if (!inNebula && playerFleet.isInHyperspace()) {
382                        numNebula = 0;
383                }
384                
385                for (int i = 0; i < numNebula; i++) {
386                        float x = random.nextFloat() * width - width/2;
387                        float y = random.nextFloat() * height - height/2;
388                        float radius = 100f + random.nextFloat() * 400f;
389                        if (inNebula) {
390                                radius += 100f + 500f * random.nextFloat();
391                        }
392                        loader.addNebula(x, y, radius);
393                }
394                
395                if (!playerFleet.isInHyperspace()) {
396                        float numAsteroidsWithinRange = countNearbyAsteroids(playerFleet);
397                        
398                        int numAsteroids = Math.min(400, (int)((numAsteroidsWithinRange + 1f) * 20f));
399                        
400                        loader.addAsteroidField(0, 0, random.nextFloat() * 360f, width,
401                                                                        20f, 70f, numAsteroids);
402                        
403                        if (numRings > 0) {
404                                int numRingAsteroids = (int) (numRings * 300 + (numRings * 600f) * random.nextFloat());
405                                //int numRingAsteroids = (int) (numRings * 1600 + (numRings * 600f) * (float) Math.random());
406                                if (numRingAsteroids > 1500) {
407                                        numRingAsteroids = 1500;
408                                }
409                                loader.addRingAsteroids(0, 0, random.nextFloat() * 360f, width,
410                                                100f, 200f, numRingAsteroids);
411                        }
412                }
413                
414                //setRandomBackground(loader);
415                loader.setBackgroundSpriteName(playerFleet.getContainingLocation().getBackgroundTextureFilename());
416//              loader.setBackgroundSpriteName("graphics/backgrounds/hyperspace_bg_cool.jpg");
417//              loader.setBackgroundSpriteName("graphics/ships/onslaught/onslaught_base.png");
418
419                if (playerFleet.getContainingLocation() == Global.getSector().getHyperspace()) {
420                        loader.setHyperspaceMode(true);
421                } else {
422                        loader.setHyperspaceMode(false);
423                }
424                
425                //addMultiplePlanets();
426                addClosestPlanet();
427        }
428        
429        protected void addClosestPlanet() {
430                float bgWidth = 2048f;
431                float bgHeight = 2048f;
432                
433                PlanetAPI planet = getClosestPlanet(context.getPlayerFleet());
434                if (planet == null) return;
435                
436                float dist = Vector2f.sub(context.getPlayerFleet().getLocation(), planet.getLocation(), new Vector2f()).length() - planet.getRadius();
437                if (dist < 0) dist = 0;
438                float baseRadius = planet.getRadius();
439                float scaleFactor = 1.5f;
440                float maxRadius = 500f;
441                float minRadius = 100f;
442                                
443//              if (planet.isStar()) {
444//                      scaleFactor = 0.01f;
445//                      maxRadius = 20f;
446//              }
447                
448                float maxDist = SINGLE_PLANET_MAX_DIST - planet.getRadius();
449                if (maxDist < 1) maxDist = 1;
450                
451                
452                boolean playerHasStation = false;
453                boolean enemyHasStation = false;
454                
455                for (FleetMemberAPI curr : context.getPlayerFleet().getFleetData().getMembersListCopy()) {
456                        if (curr.isStation()) {
457                                playerHasStation = true;
458                                break;
459                        }
460                }
461                
462                for (FleetMemberAPI curr : context.getOtherFleet().getFleetData().getMembersListCopy()) {
463                        if (curr.isStation()) {
464                                enemyHasStation = true;
465                                break;
466                        }
467                }
468                
469                float planetYOffset = 0;
470                
471                if (playerHasStation) {
472                        planetYOffset = -bgHeight / 2f * 0.5f;
473                }
474                if (enemyHasStation) {
475                        planetYOffset = bgHeight / 2f * 0.5f;
476                }
477                
478                
479                float f = (maxDist - dist) / maxDist * 0.65f + 0.35f;
480                float radius = baseRadius * f * scaleFactor;
481                if (radius > maxRadius) radius = maxRadius;
482                if (radius < minRadius) radius = minRadius;
483                loader.setPlanetBgSize(bgWidth * f, bgHeight * f);
484                loader.addPlanet(0f, planetYOffset, radius, planet, 0f, true);
485        }
486        
487        protected void addMultiplePlanets() {
488                float bgWidth = 2048f;
489                float bgHeight = 2048f;
490                loader.setPlanetBgSize(bgWidth, bgHeight);
491                
492                
493                List<NearbyPlanetData> planets = getNearbyPlanets(context.getPlayerFleet());
494                if (!planets.isEmpty()) {
495                        float maxDist = PLANET_MAX_DIST;
496                        for (NearbyPlanetData data : planets) {
497                                float dist = Vector2f.sub(context.getPlayerFleet().getLocation(), data.planet.getLocation(), new Vector2f()).length();
498                                float baseRadius = data.planet.getRadius();
499                                float scaleFactor = 1.5f;
500                                float maxRadius = 500f;
501                                
502                                if (data.planet.isStar()) {
503                                        // skip stars in combat, bright and annoying
504                                        continue;
505//                                      scaleFactor = 0.1f;
506//                                      maxRadius = 50f;
507                                }
508                                
509                                float f = (maxDist - dist) / maxDist * 0.65f + 0.35f;
510                                float radius = baseRadius * f * scaleFactor;
511                                if (radius > maxRadius) radius = maxRadius;
512                                
513                                loader.addPlanet(data.offset.x * bgWidth / PLANET_AREA_WIDTH * scaleFactor,
514                                                                 data.offset.y * bgHeight / PLANET_AREA_HEIGHT * scaleFactor,
515                                                                 radius, data.planet.getTypeId(), 0f, true);
516                        }
517                        
518                }
519        }
520        
521        
522        protected void setRandomBackground(MissionDefinitionAPI loader, Random random) {
523                // these have to be loaded using the graphics section in settings.json
524                String [] bgs = new String [] {
525                                "graphics/backgrounds/background1.jpg",
526                                "graphics/backgrounds/background2.jpg",
527                                "graphics/backgrounds/background3.jpg",
528                                "graphics/backgrounds/background4.jpg"
529                };
530                String pick = bgs[Math.min(bgs.length - 1, (int)(random.nextDouble() * bgs.length))];
531                loader.setBackgroundSpriteName(pick);
532        }
533
534        protected static String COMM = "comm_relay";
535        protected static String SENSOR = "sensor_array";
536        protected static String NAV = "nav_buoy";
537        
538        protected void addObjectives(MissionDefinitionAPI loader, int num, Random random) {
539                //if (true) return;
540                
541                objs = new ArrayList<String>(Arrays.asList(new String [] {
542                                SENSOR,
543                                SENSOR,
544                                NAV,
545                                NAV,
546                                //COMM,
547                                //COMM,
548                }));
549
550                if (num == 2) { // minimum is 3 now, so this shouldn't happen
551                        objs = new ArrayList<String>(Arrays.asList(new String [] {
552                                        SENSOR,
553                                        SENSOR,
554                                        NAV,
555                                        NAV,
556                                        COMM,
557                        }));
558                        addObjectiveAt(0.25f, 0.5f, 0f, 0f, random);
559                        addObjectiveAt(0.75f, 0.5f, 0f, 0f, random);
560                } else if (num == 3) {
561                        float r = random.nextFloat();
562                        if (r < 0.33f) {
563                                addObjectiveAt(0.25f, 0.7f, 1f, 1f, random);
564                                addObjectiveAt(0.25f, 0.3f, 1f, 1f, random);
565                                addObjectiveAt(0.75f, 0.5f, 1f, 1f, COMM, random);
566                        } else if (r < 0.67f) {
567                                addObjectiveAt(0.75f, 0.7f, 1f, 1f, random);
568                                addObjectiveAt(0.75f, 0.3f, 1f, 1f, random);
569                                addObjectiveAt(0.25f, 0.5f, 1f, 1f, COMM, random);
570                        } else {
571                                if (random.nextFloat() < 0.5f) {
572                                        addObjectiveAt(0.22f, 0.7f, 1f, 1f, random);
573                                        addObjectiveAt(0.5f, 0.5f, 1f, 1f, COMM, random);
574                                        addObjectiveAt(0.78f, 0.3f, 1f, 1f, random);
575                                } else {
576                                        addObjectiveAt(0.22f, 0.3f, 1f, 1f, random);
577                                        addObjectiveAt(0.5f, 0.5f, 1f, 1f, COMM, random);
578                                        addObjectiveAt(0.78f, 0.7f, 1f, 1f, random);
579                                }
580                        }
581                } else if (num == 4) {
582                        float r = random.nextFloat();
583                        if (r < 0.33f) {
584                                String [] maybeRelays = pickCommRelays(2, 2, false, true, true, false, random);
585                                addObjectiveAt(0.25f, 0.25f, 2f, 1f, maybeRelays[0], random);
586                                addObjectiveAt(0.25f, 0.75f, 2f, 1f, maybeRelays[1], random);
587                                addObjectiveAt(0.75f, 0.25f, 2f, 1f, maybeRelays[2], random);
588                                addObjectiveAt(0.75f, 0.75f, 2f, 1f, maybeRelays[3], random);
589                        } else if (r < 0.67f) {
590                                String [] maybeRelays = pickCommRelays(1, 2, true, false, true, false, random);
591                                addObjectiveAt(0.25f, 0.5f, 1f, 1f, maybeRelays[0], random);
592                                addObjectiveAt(0.5f, 0.75f, 1f, 1f, maybeRelays[1], random);
593                                addObjectiveAt(0.75f, 0.5f, 1f, 1f, maybeRelays[2], random);
594                                addObjectiveAt(0.5f, 0.25f, 1f, 1f, maybeRelays[3], random);
595                        } else {
596                                if (random.nextFloat() < 0.5f) {
597                                        String [] maybeRelays = pickCommRelays(1, 2, true, false, true, false, random);
598                                        addObjectiveAt(0.25f, 0.25f, 1f, 0f, maybeRelays[0], random);
599                                        addObjectiveAt(0.4f, 0.6f, 1f, 0f, maybeRelays[1], random);
600                                        addObjectiveAt(0.6f, 0.4f, 1f, 0f, maybeRelays[2], random);
601                                        addObjectiveAt(0.75f, 0.75f, 1f, 0f, maybeRelays[3], random);
602                                } else {
603                                        String [] maybeRelays = pickCommRelays(1, 2, false, true, false, true, random);
604                                        addObjectiveAt(0.25f, 0.75f, 1f, 0f, maybeRelays[0], random);
605                                        addObjectiveAt(0.4f, 0.4f, 1f, 0f, maybeRelays[1], random);
606                                        addObjectiveAt(0.6f, 0.6f, 1f, 0f, maybeRelays[2], random);
607                                        addObjectiveAt(0.75f, 0.25f, 1f, 0f, maybeRelays[3], random);
608                                }
609                        }
610                }
611        }
612        
613        protected String [] pickCommRelays(int min, int max, boolean comm1, boolean comm2, boolean comm3, boolean comm4, Random random) {
614                String [] result = new String [4];
615                
616                WeightedRandomPicker<Integer> picker = new WeightedRandomPicker<Integer>(random);
617                if (comm1) picker.add(0);
618                if (comm2) picker.add(1);
619                if (comm3) picker.add(2);
620                if (comm4) picker.add(3);
621                
622                int num = min + random.nextInt(max - min + 1);
623                
624                for (int i = 0; i < num && !picker.isEmpty(); i++) {
625                        result[picker.pickAndRemove()] = COMM;
626                }
627                return result;
628        }
629        
630        
631        protected void addEscapeObjectives(MissionDefinitionAPI loader, int num, Random random) {
632                objs = new ArrayList<String>(Arrays.asList(new String [] {
633                                SENSOR,
634                                SENSOR,
635                                NAV,
636                                NAV,
637                                COMM,
638                }));
639                
640                if (num == 2) {
641                        float r = random.nextFloat();
642                        if (r < 0.33f) {
643                                addObjectiveAt(0.25f, 0.25f, 1f, 1f, random);
644                                addObjectiveAt(0.75f, 0.75f, 1f, 1f, random);
645                        } else if (r < 0.67f) {
646                                addObjectiveAt(0.75f, 0.25f, 1f, 1f, random);
647                                addObjectiveAt(0.25f, 0.75f, 1f, 1f, random);
648                        } else {
649                                addObjectiveAt(0.5f, 0.25f, 4f, 2f, random);
650                                addObjectiveAt(0.5f, 0.75f, 4f, 2f, random);
651                        }
652                } else if (num == 3) {
653                        float r = random.nextFloat();
654                        if (r < 0.33f) {
655                                addObjectiveAt(0.25f, 0.75f, 1f, 1f, random);
656                                addObjectiveAt(0.25f, 0.25f, 1f, 1f, random);
657                                addObjectiveAt(0.75f, 0.5f, 1f, 6f, random);
658                        } else if (r < 0.67f) {
659                                addObjectiveAt(0.25f, 0.5f, 1f, 6f, random);
660                                addObjectiveAt(0.75f, 0.75f, 1f, 1f, random);
661                                addObjectiveAt(0.75f, 0.25f, 1f, 1f, random);
662                        } else {
663                                addObjectiveAt(0.5f, 0.25f, 4f, 1f, random);
664                                addObjectiveAt(0.5f, 0.5f, 4f, 1f, random);
665                                addObjectiveAt(0.5f, 0.75f, 4f, 1f, random);
666                        }
667                } else if (num == 4) {
668                        float r = random.nextFloat();
669                        if (r < 0.33f) {
670                                addObjectiveAt(0.25f, 0.25f, 1f, 1f, random);
671                                addObjectiveAt(0.25f, 0.75f, 1f, 1f, random);
672                                addObjectiveAt(0.75f, 0.25f, 1f, 1f, random);
673                                addObjectiveAt(0.75f, 0.75f, 1f, 1f, random);
674                        } else if (r < 0.67f) {
675                                addObjectiveAt(0.35f, 0.25f, 2f, 0f, random);
676                                addObjectiveAt(0.65f, 0.35f, 2f, 0f, random);
677                                addObjectiveAt(0.5f, 0.6f, 4f, 1f, random);
678                                addObjectiveAt(0.5f, 0.8f, 4f, 1f, random);
679                        } else {
680                                addObjectiveAt(0.65f, 0.25f, 2f, 0f, random);
681                                addObjectiveAt(0.35f, 0.35f, 2f, 0f, random);
682                                addObjectiveAt(0.5f, 0.6f, 4f, 1f, random);
683                                addObjectiveAt(0.5f, 0.8f, 4f, 1f, random);
684                        }
685                }
686        }       
687
688        protected void addObjectiveAt(float xMult, float yMult, float xOff, float yOff, Random random) {
689                addObjectiveAt(xMult, yMult, xOff, yOff, null, random);
690        }
691        protected void addObjectiveAt(float xMult, float yMult, float xOff, float yOff, String type, Random random) {
692                //String type = pickAny();
693                if (type == null) {
694                        type = pickAny(random);
695                        if (objs != null && objs.size() > 0) {
696                                int index = (int) (random.nextDouble() * objs.size());
697                                type = objs.remove(index); 
698                        }
699                }
700                
701                float minX = -width/2 + xPad;
702                float minY = -height/2 + yPad;
703                
704                float x = (width - xPad * 2f) * xMult + minX;
705                float y = (height - yPad * 2f) * yMult + minY;
706                
707                x = ((int) x / 1000) * 1000f;
708                y = ((int) y / 1000) * 1000f;
709                
710                float offsetX = Math.round((random.nextFloat() - 0.5f) * xOff * 1f) * 1000f;
711                float offsetY = Math.round((random.nextFloat() - 0.5f) * yOff * 1f) * 1000f;
712                
713//              offsetX = 0;
714//              offsetY = 0;
715                
716                float xDir = (float) Math.signum(offsetX);
717                float yDir = (float) Math.signum(offsetY);
718                
719                if (xDir == prevXDir && xOff > 0) {
720                        xDir = -xDir;
721                        offsetX = Math.abs(offsetX) * -prevXDir;
722                }
723                
724                if (yDir == prevYDir && yOff > 0) {
725                        yDir = -yDir;
726                        offsetY = Math.abs(offsetY) * -prevYDir;
727                }
728                
729                prevXDir = xDir;
730                prevYDir = yDir;
731                
732                x += offsetX;
733                y += offsetY;
734                
735                loader.addObjective(x, y, type);
736                
737                if (random.nextFloat() > 0.6f && loader.hasNebula()) {
738                        float nebulaSize = random.nextFloat() * 1500f + 500f;
739                        loader.addNebula(x, y, nebulaSize);
740                }
741        }
742        
743        protected String pickAny(Random random) {
744                float r = random.nextFloat();
745                if (r < 0.33f) return "nav_buoy";
746                else if (r < 0.67f) return "sensor_array";
747                else return "comm_relay"; 
748        }
749
750        protected float countNearbyAsteroids(CampaignFleetAPI playerFleet) {
751                float numAsteroidsWithinRange = 0;
752                LocationAPI loc = playerFleet.getContainingLocation();
753                if (loc instanceof StarSystemAPI) {
754                        StarSystemAPI system = (StarSystemAPI) loc;
755                        List<SectorEntityToken> asteroids = system.getAsteroids();
756                        for (SectorEntityToken asteroid : asteroids) {
757                                float range = Vector2f.sub(playerFleet.getLocation(), asteroid.getLocation(), new Vector2f()).length();
758                                if (range < 300) numAsteroidsWithinRange ++;
759                        }
760                }
761                return numAsteroidsWithinRange;
762        }
763        
764        protected static class NearbyPlanetData {
765                protected Vector2f offset;
766                protected PlanetAPI planet;
767                public NearbyPlanetData(Vector2f offset, PlanetAPI planet) {
768                        this.offset = offset;
769                        this.planet = planet;
770                }
771        }
772        
773        protected static float PLANET_AREA_WIDTH = 2000;
774        protected static float PLANET_AREA_HEIGHT = 2000;
775        protected static float PLANET_MAX_DIST = (float) Math.sqrt(PLANET_AREA_WIDTH/2f * PLANET_AREA_WIDTH/2f + PLANET_AREA_HEIGHT/2f * PLANET_AREA_WIDTH/2f);
776        
777        protected static float SINGLE_PLANET_MAX_DIST = 1000f;
778        
779        protected List<NearbyPlanetData> getNearbyPlanets(CampaignFleetAPI playerFleet) {
780                LocationAPI loc = playerFleet.getContainingLocation();
781                List<NearbyPlanetData> result = new ArrayList<NearbyPlanetData>();
782                if (loc instanceof StarSystemAPI) {
783                        StarSystemAPI system = (StarSystemAPI) loc;
784                        List<PlanetAPI> planets = system.getPlanets();
785                        for (PlanetAPI planet : planets) {
786                                float diffX = planet.getLocation().x - playerFleet.getLocation().x;
787                                float diffY = planet.getLocation().y - playerFleet.getLocation().y;
788                                
789                                if (Math.abs(diffX) < PLANET_AREA_WIDTH/2f && Math.abs(diffY) < PLANET_AREA_HEIGHT/2f) {
790                                        result.add(new NearbyPlanetData(new Vector2f(diffX, diffY), planet));
791                                }
792                        }
793                }
794                return result;
795        }
796        
797        protected PlanetAPI getClosestPlanet(CampaignFleetAPI playerFleet) {
798                LocationAPI loc = playerFleet.getContainingLocation();
799                PlanetAPI closest = null;
800                float minDist = Float.MAX_VALUE;
801                if (loc instanceof StarSystemAPI) {
802                        StarSystemAPI system = (StarSystemAPI) loc;
803                        List<PlanetAPI> planets = system.getPlanets();
804                        for (PlanetAPI planet : planets) {
805                                if (planet.isStar()) continue;
806                                if (Planets.PLANET_LAVA.equals(planet.getTypeId())) continue;
807                                if (Planets.PLANET_LAVA_MINOR.equals(planet.getTypeId())) continue;
808                                if (planet.getSpec().isDoNotShowInCombat()) continue;
809                                
810                                float dist = Vector2f.sub(context.getPlayerFleet().getLocation(), planet.getLocation(), new Vector2f()).length();
811                                if (dist < minDist && dist < SINGLE_PLANET_MAX_DIST) {
812                                        closest = planet;
813                                        minDist = dist;
814                                }
815                        }
816                }
817                return closest;
818        }
819}
820
821
822
823