001package com.fs.starfarer.api.impl.campaign.abilities;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import java.awt.Color;
007
008import org.lwjgl.util.vector.Vector2f;
009
010import com.fs.starfarer.api.EveryFrameScript;
011import com.fs.starfarer.api.Global;
012import com.fs.starfarer.api.campaign.BattleAPI;
013import com.fs.starfarer.api.campaign.CampaignFleetAPI;
014import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination;
015import com.fs.starfarer.api.campaign.NascentGravityWellAPI;
016import com.fs.starfarer.api.campaign.SectorEntityToken;
017import com.fs.starfarer.api.campaign.StarSystemAPI;
018import com.fs.starfarer.api.campaign.econ.MarketAPI;
019import com.fs.starfarer.api.fleet.FleetMemberAPI;
020import com.fs.starfarer.api.impl.campaign.ids.Pings;
021import com.fs.starfarer.api.impl.campaign.ids.Stats;
022import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
023import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel;
024import com.fs.starfarer.api.ui.LabelAPI;
025import com.fs.starfarer.api.ui.TooltipMakerAPI;
026import com.fs.starfarer.api.util.Misc;
027import com.fs.starfarer.api.util.Misc.FleetMemberDamageLevel;
028
029public class FractureJumpAbility extends BaseDurationAbility {
030
031        public static float CR_COST_MULT = 0.1f;
032        public static float FUEL_USE_MULT = 1f;
033        
034        
035        public static float NASCENT_JUMP_DIST = 50f;
036
037        protected boolean canUseToJumpToHyper() {
038                return true;
039        }
040        
041        protected boolean canUseToJumpToSystem() {
042                return true;
043        }
044        
045        protected Boolean primed = null;
046        protected NascentGravityWellAPI well = null;
047        protected EveryFrameScript ping = null;
048        
049        @Override
050        protected void activateImpl() {
051                //if (Global.getSector().isPaused()) return;
052                
053                CampaignFleetAPI fleet = getFleet();
054                if (fleet == null) return;
055                
056                if (fleet.isInHyperspaceTransition()) return;
057                
058                if (fleet.isInHyperspace() && canUseToJumpToSystem()) { 
059                        NascentGravityWellAPI well = getNearestWell(NASCENT_JUMP_DIST);
060                        if (well == null || well.getTarget() == null) return;
061                        
062                        this.well = well;
063                        ping = Global.getSector().addPing(fleet, Pings.TRANSVERSE_JUMP);
064                        primed = true;
065                } else if (!fleet.isInHyperspace() && canUseToJumpToHyper() &&
066                                fleet.getContainingLocation() instanceof StarSystemAPI) {
067                        ping = Global.getSector().addPing(fleet, Pings.TRANSVERSE_JUMP);
068                        primed = true;
069                } else {
070                        deactivate();
071                }
072        }
073
074        @Override
075        public void deactivate() {
076                if (ping != null) {
077                        Global.getSector().removeScript(ping);
078                        ping = null;
079                }
080                super.deactivate();
081        }
082
083        @Override
084        protected void applyEffect(float amount, float level) {
085                CampaignFleetAPI fleet = getFleet();
086                if (fleet == null) return;
087                
088                if (level > 0 && level < 1 && amount > 0) {
089                        float activateSeconds = getActivationDays() * Global.getSector().getClock().getSecondsPerDay();
090                        float speed = fleet.getVelocity().length();
091                        float acc = Math.max(speed, 200f)/activateSeconds + fleet.getAcceleration();
092                        float ds = acc * amount;
093                        if (ds > speed) ds = speed;
094                        Vector2f dv = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(fleet.getVelocity()));
095                        dv.scale(ds);
096                        fleet.setVelocity(fleet.getVelocity().x - dv.x, fleet.getVelocity().y - dv.y);
097                        return;
098                }
099                
100                if (level == 1 && primed != null) {
101                        float dist = Float.MAX_VALUE;
102                        if (well != null) {
103                                dist = Misc.getDistance(fleet, well) - well.getRadius() - fleet.getRadius();
104                        }
105                        if (well != null && fleet.isInHyperspace() && canUseToJumpToSystem() && dist < 500f) { 
106                                SectorEntityToken planet = well.getTarget();
107                                Vector2f loc = Misc.getPointAtRadius(planet.getLocation(), planet.getRadius() + 200f + fleet.getRadius());
108                                SectorEntityToken token = planet.getContainingLocation().createToken(loc.x, loc.y);
109                                
110                                JumpDestination dest = new JumpDestination(token, null);
111                                Global.getSector().doHyperspaceTransition(fleet, fleet, dest);
112                        } else if (!fleet.isInHyperspace() && canUseToJumpToHyper() &&
113                                        fleet.getContainingLocation() instanceof StarSystemAPI) {
114                                float crCostFleetMult = getCRCostMult(fleet);
115                                if (crCostFleetMult > 0) {
116                                        for (FleetMemberAPI member : getNonReadyShips()) {
117                                                if ((float) Math.random() < EmergencyBurnAbility.ACTIVATION_DAMAGE_PROB) {
118                                                        Misc.applyDamage(member, null, FleetMemberDamageLevel.LOW, false, null, null,
119                                                                        true, null, member.getShipName() + " suffers damage from Transverse Jump activation");
120                                                }
121                                        }
122                                        for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
123                                                float crLoss = member.getDeployCost() * CR_COST_MULT * crCostFleetMult;
124                                                member.getRepairTracker().applyCREvent(-crLoss, "Transverse jump");
125                                        }
126                                        String key = "$makeTranverseJumpCostMoreCROnce";
127                                        fleet.getMemoryWithoutUpdate().unset(key);
128                                }
129                                
130                                float cost = computeFuelCost();
131                                fleet.getCargo().removeFuel(cost);
132                                
133                                
134                                StarSystemAPI system = (StarSystemAPI) fleet.getContainingLocation();
135                                
136                                Vector2f offset = Vector2f.sub(fleet.getLocation(), system.getCenter().getLocation(), new Vector2f());
137                                float maxInSystem = 20000f;
138                                float maxInHyper = 2000f;
139                                float f = offset.length() / maxInSystem;
140                                //if (f > 1) f = 1;
141                                if (f > 0.5f) f = 0.5f;
142                                
143                                float angle = Misc.getAngleInDegreesStrict(offset);
144                                
145                                Vector2f destOffset = Misc.getUnitVectorAtDegreeAngle(angle);
146                                destOffset.scale(f * maxInHyper);
147                                
148                                Vector2f.add(system.getLocation(), destOffset, destOffset);
149                                SectorEntityToken token = Global.getSector().getHyperspace().createToken(destOffset.x, destOffset.y);
150                                
151                                JumpDestination dest = new JumpDestination(token, null);
152                                Global.getSector().doHyperspaceTransition(fleet, fleet, dest);
153                        }
154                        
155                        primed = null;
156                        well = null;
157                }
158        }
159        
160        @Override
161        protected String getActivationText() {
162                return super.getActivationText();
163                //return "Initiating jump";
164        }
165
166
167        @Override
168        protected void deactivateImpl() {
169                cleanupImpl();
170        }
171        
172        @Override
173        protected void cleanupImpl() {
174                CampaignFleetAPI fleet = getFleet();
175                if (fleet == null) return;
176        }
177        
178        @Override
179        public boolean isUsable() {
180                if (!super.isUsable()) return false;
181                if (getFleet() == null) return false;
182                
183                CampaignFleetAPI fleet = getFleet();
184                
185                if (fleet.isInHyperspaceTransition()) return false;
186                
187                if (TutorialMissionIntel.isTutorialInProgress()) return false;
188                
189                if (canUseToJumpToSystem() && fleet.isInHyperspace() && getNearestWell(NASCENT_JUMP_DIST) != null) {
190                        return true;
191                }
192                
193                if (canUseToJumpToHyper() && !fleet.isInHyperspace()) {
194                        //if (getNonReadyShips().isEmpty() &&
195                        if ((getFleet().isAIMode() || computeFuelCost() <= getFleet().getCargo().getFuel())) {
196                                return true;
197                        }
198                }
199                
200                return false;
201        }
202        
203        public NascentGravityWellAPI getNearestWell(float maxDist) {
204                CampaignFleetAPI fleet = getFleet();
205                if (fleet == null) return null;
206                if (!fleet.isInHyperspace()) return null;
207                
208                float minDist = Float.MAX_VALUE;
209                NascentGravityWellAPI closest = null;
210                List<Object> wells = fleet.getContainingLocation().getEntities(NascentGravityWellAPI.class);
211                for (Object o : wells) {
212                        NascentGravityWellAPI well = (NascentGravityWellAPI) o;
213                        float dist = Misc.getDistance(well.getLocation(), fleet.getLocation());
214                        dist -= well.getRadius() + fleet.getRadius();
215                        if (dist > maxDist) continue;
216                        if (dist < minDist) {
217                                minDist = dist;
218                                closest = well;
219                        }
220                }
221                return closest;
222        }
223
224        
225        @Override
226        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
227                CampaignFleetAPI fleet = getFleet();
228                if (fleet == null) return;
229                
230                Color gray = Misc.getGrayColor();
231                Color highlight = Misc.getHighlightColor();
232                Color fuel = Global.getSettings().getColor("progressBarFuelColor");
233                Color bad = Misc.getNegativeHighlightColor();
234                
235                if (!Global.CODEX_TOOLTIP_MODE) {
236                        LabelAPI title = tooltip.addTitle("Transverse Jump");
237                } else {
238                        tooltip.addSpacer(-10f);
239                }
240
241                float pad = 10f;
242
243                NascentGravityWellAPI well = getNearestWell(NASCENT_JUMP_DIST);
244                
245                tooltip.addPara("Jump into hyperspace without the use of a jump-point, or " +
246                                                "jump into a star system across the hyperspace boundary near a nascent gravity well, " +
247                                                "emerging near the entity corresponding to the gravity well.", pad);
248                
249                
250                if (Global.CODEX_TOOLTIP_MODE) {
251                        String years = "year's";
252                        if (FUEL_USE_MULT != 1) years = "years'";
253                        tooltip.addPara("Jumping into hyperspace consumes %s light " + years + " worth of fuel and reduces the combat readiness "
254                                        + "of all ships by %s of a combat deployment. " +
255                                        "Jumping into a star system is free.", pad, 
256                                        highlight,
257                                        "" + Misc.getRoundedValue(FUEL_USE_MULT),
258                                        "" + (int) Math.round(CR_COST_MULT * 100f) + "%");
259                        
260                        tooltip.addPara("Ships with insufficient combat readiness may suffer damage when the ability is activated.", pad);
261                } else {
262                        float fuelCost = computeFuelCost();
263                        float supplyCost = computeSupplyCost();
264                        
265                        if (supplyCost > 0) {
266                                tooltip.addPara("Jumping into hyperspace consumes %s fuel and slightly reduces the combat readiness" +
267                                                        " of all ships, costing up to %s supplies to recover. Jumping into a star system is free.", pad, 
268                                                        highlight,
269                                                        Misc.getRoundedValueMaxOneAfterDecimal(fuelCost),
270                                                        Misc.getRoundedValueMaxOneAfterDecimal(supplyCost));
271                        } else {
272                                tooltip.addPara("Jumping into hyperspace consumes %s fuel. Jumping into a star system is free.", pad, 
273                                                highlight,
274                                                Misc.getRoundedValueMaxOneAfterDecimal(fuelCost));
275                        }
276                        
277                        
278                        if (TutorialMissionIntel.isTutorialInProgress()) { 
279                                tooltip.addPara("Can not be used right now.", bad, pad);
280                        }
281                        
282                        if (!fleet.isInHyperspace()) {
283                                if (fuelCost > fleet.getCargo().getFuel()) {
284                                        tooltip.addPara("Not enough fuel.", bad, pad);
285                                }
286                        
287                                List<FleetMemberAPI> nonReady = getNonReadyShips();
288                                if (!nonReady.isEmpty()) {
289                                        //tooltip.addPara("Not all ships have enough combat readiness to initiate an emergency burn. Ships that require higher CR:", pad);
290                                        tooltip.addPara("Some ships don't have enough combat readiness to safely initiate a transverse jump " +
291                                                                        "and may suffer damage if the ability is activated:", pad, 
292                                                                        Misc.getNegativeHighlightColor(), "may suffer damage");
293                                        int j = 0;
294                                        int max = 4;
295                                        float initPad = 5f;
296                                        for (FleetMemberAPI member : nonReady) {
297                                                if (j >= max) {
298                                                        if (nonReady.size() > max + 1) {
299                                                                tooltip.addPara(BaseIntelPlugin.INDENT + "... and several other ships", initPad);
300                                                                break;
301                                                        }
302                                                }
303                                                String str = "";
304                                                if (!member.isFighterWing()) {
305                                                        str += member.getShipName() + ", ";
306                                                        str += member.getHullSpec().getHullNameWithDashClass();
307                                                } else {
308                                                        str += member.getVariant().getFullDesignationWithHullName();
309                                                }
310                                                
311                                                tooltip.addPara(BaseIntelPlugin.INDENT + str, initPad);
312                                                initPad = 0f;
313                                                j++;
314                                        }
315                                }
316                                
317        //                      List<FleetMemberAPI> nonReady = getNonReadyShips();
318        //                      if (!nonReady.isEmpty()) {
319        //                              tooltip.addPara("Not all ships have enough combat readiness to initiate a transverse jump. Ships that require higher CR:", pad);
320        //                              tooltip.beginGridFlipped(getTooltipWidth(), 1, 30, pad);
321        //                              //tooltip.setGridLabelColor(bad);
322        //                              int j = 0;
323        //                              int max = 7;
324        //                              for (FleetMemberAPI member : nonReady) {
325        //                                      if (j >= max) {
326        //                                              if (nonReady.size() > max + 1) {
327        //                                                      tooltip.addToGrid(0, j++, "... and several other ships", "", bad);
328        //                                                      break;
329        //                                              }
330        //                                      }
331        //                                      float crLoss = member.getDeployCost() * CR_COST_MULT;
332        //                                      String cost = "" + Math.round(crLoss * 100) + "%";
333        //                                      String str = "";
334        //                                      if (!member.isFighterWing()) {
335        //                                              str += member.getShipName() + ", ";
336        //                                              str += member.getHullSpec().getHullNameWithDashClass();
337        //                                      } else {
338        //                                              str += member.getVariant().getFullDesignationWithHullName();
339        //                                      }
340        //                                      tooltip.addToGrid(0, j++, str, cost, bad);
341        //                              }
342        //                              tooltip.addGrid(3f);
343        //                      }
344                        }
345        
346                        if (fleet.isInHyperspace()) {
347                                if (well == null) {
348                                        tooltip.addPara("Must be near a nascent gravity well.", bad, pad);
349                                }
350                        }
351                }
352                
353                addIncompatibleToTooltip(tooltip, expanded);
354        }
355
356        public boolean hasTooltip() {
357                return true;
358        }
359        
360        @Override
361        public void fleetLeftBattle(BattleAPI battle, boolean engagedInHostilities) {
362                if (engagedInHostilities) {
363                        deactivate();
364                }
365        }
366        
367        @Override
368        public void fleetOpenedMarket(MarketAPI market) {
369                deactivate();
370        }
371        
372        
373        protected List<FleetMemberAPI> getNonReadyShips() {
374                List<FleetMemberAPI> result = new ArrayList<FleetMemberAPI>();
375                CampaignFleetAPI fleet = getFleet();
376                if (fleet == null) return result;
377                
378                //float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.EMERGENCY_BURN_CR_MULT);
379                //float crCostFleetMult = 1f;
380                float crCostFleetMult = getCRCostMult(fleet);
381                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
382                        //if (member.isMothballed()) continue;
383                        float crLoss = member.getDeployCost() * CR_COST_MULT * crCostFleetMult;
384                        if (Math.round(member.getRepairTracker().getCR() * 100) < Math.round(crLoss * 100)) {
385                                result.add(member);
386                        }
387                }
388                return result;
389        }
390
391        protected float computeFuelCost() {
392                CampaignFleetAPI fleet = getFleet();
393                if (fleet == null) return 0f;
394                
395                float cost = fleet.getLogistics().getFuelCostPerLightYear() * FUEL_USE_MULT;
396                return cost;
397        }
398        
399        protected float getCRCostMult(CampaignFleetAPI fleet) {
400                float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.DIRECT_JUMP_CR_MULT);
401                String key = "$makeTranverseJumpCostMoreCROnce";
402                if (fleet.getMemoryWithoutUpdate().contains(key)) {
403                        crCostFleetMult = 20f;
404                }
405                return crCostFleetMult;
406        }
407        
408        protected float computeSupplyCost() {
409                CampaignFleetAPI fleet = getFleet();
410                if (fleet == null) return 0f;
411                
412                //float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.EMERGENCY_BURN_CR_MULT);
413                //float crCostFleetMult = 1f;
414                float crCostFleetMult = getCRCostMult(fleet);
415                
416                float cost = 0f;
417                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
418                        cost += member.getDeploymentCostSupplies() * CR_COST_MULT * crCostFleetMult;
419                }
420                return cost;
421        }
422        
423        
424        
425        protected boolean showAlarm() {
426                if (getFleet() != null && getFleet().isInHyperspace()) return false;
427                return !getNonReadyShips().isEmpty() && !isOnCooldown() && !isActiveOrInProgress() && isUsable();
428        }
429        
430//      @Override
431//      public boolean isUsable() {
432//              return super.isUsable() && 
433//                                      getFleet() != null && 
434//                                      //getNonReadyShips().isEmpty() &&
435//                                      (getFleet().isAIMode() || computeFuelCost() <= getFleet().getCargo().getFuel());
436//      }
437        
438        @Override
439        public float getCooldownFraction() {
440                if (showAlarm()) {
441                        return 0f;
442                }
443                return super.getCooldownFraction();
444        }
445        @Override
446        public boolean showCooldownIndicator() {
447                return super.showCooldownIndicator();
448        }
449        @Override
450        public boolean isOnCooldown() {
451                return super.getCooldownFraction() < 1f;
452        }
453
454        @Override
455        public Color getCooldownColor() {
456                if (showAlarm()) {
457                        Color color = Misc.getNegativeHighlightColor();
458                        return Misc.scaleAlpha(color, Global.getSector().getCampaignUI().getSharedFader().getBrightness() * 0.5f);
459                }
460                return super.getCooldownColor();
461        }
462
463        @Override
464        public boolean isCooldownRenderingAdditive() {
465                if (showAlarm()) {
466                        return true;
467                }
468                return false;
469        }
470}
471
472
473
474
475