001package com.fs.starfarer.api.impl.campaign;
002
003import java.util.HashMap;
004import java.util.List;
005import java.util.Map;
006
007import java.awt.Color;
008
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.campaign.BuffManagerAPI.Buff;
011import com.fs.starfarer.api.campaign.CampaignUIAPI.CoreUITradeMode;
012import com.fs.starfarer.api.campaign.FleetDataAPI;
013import com.fs.starfarer.api.campaign.econ.MarketAPI;
014import com.fs.starfarer.api.combat.HullModEffect;
015import com.fs.starfarer.api.combat.MutableShipStatsAPI;
016import com.fs.starfarer.api.combat.MutableStat;
017import com.fs.starfarer.api.combat.MutableStat.StatMod;
018import com.fs.starfarer.api.combat.ShipAPI;
019import com.fs.starfarer.api.combat.ShipAPI.HullSize;
020import com.fs.starfarer.api.combat.ShipVariantAPI;
021import com.fs.starfarer.api.fleet.FleetMemberAPI;
022import com.fs.starfarer.api.loading.HullModSpecAPI;
023import com.fs.starfarer.api.ui.TooltipMakerAPI;
024
025public class TowCable implements HullModEffect {
026        
027        public static final String HULLMOD_ID = "tow_cable";
028        
029        public void init(HullModSpecAPI spec) {
030                
031        }
032        
033        public static class TowCableBuff implements Buff {
034                //public boolean wasApplied = false;
035                private String buffId;
036                private int frames = 0;
037                
038                public TowCableBuff(String buffId) {
039                        this.buffId = buffId; 
040                }
041                
042                public boolean isExpired() {
043                        //return wasApplied;
044                        return frames >= 2;
045                }
046                public String getId() {
047                        return buffId;
048                }
049                public void apply(FleetMemberAPI member) {
050                        // this ensures the buff lasts for exactly one frame unless wasApplied is reset (as it is later)
051                        //wasApplied = true;
052                        member.getStats().getMaxBurnLevel().modifyFlat(buffId, 1);
053                }
054                public void advance(float days) {
055                        frames++;
056                }
057        };
058        
059        
060        public TowCable() {
061
062        }
063        
064        public void advanceInCampaign(FleetMemberAPI member, float amount) {
065                if (member.getFleetData() == null) return;
066                if (member.getFleetData().getFleet() == null) return;
067                if (!member.getFleetData().getFleet().isPlayerFleet()) return;
068                
069                
070                if (!member.getVariant().getHullMods().contains(HULLMOD_ID)) {
071                        cleanUpTowCableBuffBy(member);
072                        return;
073                }
074                
075                if (!member.canBeDeployedForCombat()) {
076                        cleanUpTowCableBuffBy(member);
077                        return;
078                }
079                
080                FleetDataAPI data = member.getFleetData();
081                List<FleetMemberAPI> all = data.getMembersListCopy();
082                
083                int numCables = 0;
084                int thisCableIndex = -1;
085                for (FleetMemberAPI curr : all) {
086                        if (!curr.canBeDeployedForCombat()) continue;
087                        if (curr.getVariant().getHullMods().contains(HULLMOD_ID)) {
088                                if (curr == member) {
089                                        thisCableIndex = numCables;
090                                }
091                                numCables++;
092                        }
093                }
094                if (numCables <= 0 || thisCableIndex == -1) {
095                        cleanUpTowCableBuffBy(member);
096                        return;
097                }
098                
099                TowCableBuff buff = getTowCableBuffBy(member, true);
100                Map<FleetMemberAPI, Integer> cables = new HashMap<FleetMemberAPI, Integer>();
101                float towSpeed = member.getStats().getMaxBurnLevel().getModifiedValue();
102                FleetMemberAPI thisCableTarget = null;
103                
104                for (int cableIndex = 0; cableIndex < numCables; cableIndex++) {
105                        FleetMemberAPI slowest = getSlowest(all, towSpeed, cables);
106                        if (slowest == null) break;
107                        //slowest.getStats().getMaxBurnLevel().getModifiedValue()
108                        Integer bonus = cables.get(slowest);
109                        if (bonus == null) bonus = new Integer(0);
110                        bonus++;
111                        cables.put(slowest, bonus);
112                        
113                        if (cableIndex == thisCableIndex) {
114                                thisCableTarget = slowest;
115                                Buff existing = slowest.getBuffManager().getBuff(buff.getId());
116                                if (existing == buff) {
117                                        // make sure it's using the same buff rather than reapplying it,
118                                        // which would trigger a full stat recompute for the entire FLEET every frame
119                                        //buff.wasApplied = false;
120                                        buff.frames = 0;
121                                        //System.out.println("renewed on " + slowest);
122                                } else {
123                                        //buff.wasApplied = false;
124                                        buff.frames = 0;
125                                        slowest.getBuffManager().addBuff(buff);
126                                        //System.out.println("Num: " + slowest.getBuffManager().getBuffs().size());
127                                        //System.out.println("added to " + slowest);
128                                }
129                                break;
130                        }
131                }
132                
133                for (FleetMemberAPI curr : all) {
134                        if (curr != thisCableTarget) {
135                                curr.getBuffManager().removeBuff(buff.getId());
136                        }
137                }
138        }
139        
140        private FleetMemberAPI getSlowest(List<FleetMemberAPI> all, float speedCutoff, Map<FleetMemberAPI, Integer> cables) {
141                FleetMemberAPI slowest = null;
142                float minLevel = Float.MAX_VALUE;
143                for (FleetMemberAPI curr : all) {
144                        if (!isSuitable(curr)) continue;
145                        
146                        float baseBurn = getMaxBurnWithoutCables(curr);
147                        Integer bonus = cables.get(curr);
148                        if (bonus == null) bonus = new Integer(0);
149                        
150                        if (bonus >= getMaxCablesFor(curr)) continue;
151                        
152                        float burnLevel = baseBurn + bonus;
153                        
154                        if (burnLevel >= speedCutoff) continue;
155                
156                        if (burnLevel < minLevel) {
157                                minLevel = burnLevel;
158                                slowest = curr;
159                        }
160                }
161                return slowest;         
162        }
163        
164        private int getMaxCablesFor(FleetMemberAPI member) {
165//              switch (member.getHullSpec().getHullSize()) {
166//              case CAPITAL_SHIP: return 4;
167//              case CRUISER: return 3;
168//              case DESTROYER: return 2;
169//              case FRIGATE: return 1;
170//              }
171                return 1;
172        }
173        
174        private float getMaxBurnWithoutCables(FleetMemberAPI member) {
175                MutableStat burn = member.getStats().getMaxBurnLevel();
176                float val = burn.getModifiedValue();
177                float sub = 0;
178                for (StatMod mod : burn.getFlatMods().values()) {
179                        if (mod.getSource().startsWith(HULLMOD_ID)) sub++;
180                }
181                return Math.max(0, val - sub);
182        }
183        
184        private boolean isSuitable(FleetMemberAPI member) {
185                if (member.isFighterWing()) return false;
186                return true;
187        }
188        
189        private void cleanUpTowCableBuffBy(FleetMemberAPI member) {
190                if (member.getFleetData() == null) return;
191                FleetDataAPI data = member.getFleetData();
192                TowCableBuff buff = getTowCableBuffBy(member, false);
193                if (buff != null) {
194                        for (FleetMemberAPI curr : data.getMembersListCopy()) {
195                                curr.getBuffManager().removeBuff(buff.getId());
196                        }
197                }
198        }
199        
200        /**
201         * One instance of the buff object per ship with a Tow Cable.
202         */
203        public static final String TOW_CABLE_KEY = "TowCable_PersistentBuffs";
204        
205        @SuppressWarnings("unchecked")
206        private TowCableBuff getTowCableBuffBy(FleetMemberAPI member, boolean createIfMissing) {
207                Map<FleetMemberAPI, TowCableBuff> buffs;
208                if (Global.getSector().getPersistentData().containsKey(TOW_CABLE_KEY)) {
209                        buffs = (Map<FleetMemberAPI, TowCableBuff>) Global.getSector().getPersistentData().get(TOW_CABLE_KEY);
210                } else {
211                        buffs = new HashMap<FleetMemberAPI, TowCableBuff>();
212                        Global.getSector().getPersistentData().put(TOW_CABLE_KEY, buffs);
213                }
214                
215                //new HashMap<FleetMemberAPI, TowCableBuff>();
216                TowCableBuff buff = buffs.get(member);
217                if (buff == null && createIfMissing) {
218                        String id = HULLMOD_ID + "_" + member.getId();
219                        buff = new TowCableBuff(id);
220                        buffs.put(member, buff);
221                }
222                return buff;
223        }
224        
225
226        public void advanceInCombat(ShipAPI ship, float amount) {
227        }
228        
229        public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
230        }
231        public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) {
232        }
233        public boolean isApplicableToShip(ShipAPI ship) {
234                return true;
235        }
236
237        public String getDescriptionParam(int index, HullSize hullSize) {
238                return null;
239        }
240
241        public String getUnapplicableReason(ShipAPI ship) {
242                return null;
243        }
244
245        public boolean affectsOPCosts() {
246                return false;
247        }
248
249        public String getDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
250                return getDescriptionParam(index, hullSize);
251        }
252
253        public boolean canBeAddedOrRemovedNow(ShipAPI ship, MarketAPI marketOrNull, CoreUITradeMode mode) {
254                return true;
255        }
256
257        public String getCanNotBeInstalledNowReason(ShipAPI ship, MarketAPI marketOrNull, CoreUITradeMode mode) {
258                return null;
259        }
260
261        public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
262                return true;
263        }
264        public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, ShipAPI ship, float width, boolean isForModSpec) {
265                
266        }
267
268        public void applyEffectsToFighterSpawnedByShip(ShipAPI fighter, ShipAPI ship, String id) {
269                
270        }
271
272        public Color getBorderColor() {
273                // TODO Auto-generated method stub
274                return null;
275        }
276
277        public Color getNameColor() {
278                // TODO Auto-generated method stub
279                return null;
280        }
281
282        public int getDisplaySortOrder() {
283                return 100;
284        }
285        
286        public int getDisplayCategoryIndex() {
287                return -1;
288        }
289
290        public boolean hasSModEffectSection(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
291                return false;
292        }
293
294        public void addSModSection(TooltipMakerAPI tooltip, HullSize hullSize, ShipAPI ship, float width,
295                        boolean isForModSpec) {
296                
297        }
298
299        public void addSModEffectSection(TooltipMakerAPI tooltip, HullSize hullSize, ShipAPI ship, float width,
300                        boolean isForModSpec, boolean isForBuildInList) {
301                // TODO Auto-generated method stub
302                
303        }
304
305        public void addSModSection(TooltipMakerAPI tooltip, HullSize hullSize, ShipAPI ship, float width,
306                        boolean isForModSpec, boolean isForBuildInList) {
307                // TODO Auto-generated method stub
308                
309        }
310
311        public boolean hasSModEffect() {
312                // TODO Auto-generated method stub
313                return false;
314        }
315
316        public String getSModDescriptionParam(int index, HullSize hullSize) {
317                // TODO Auto-generated method stub
318                return null;
319        }
320
321        public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
322                // TODO Auto-generated method stub
323                return null;
324        }
325
326        public float getTooltipWidth() {
327                return 0;
328        }
329
330        public boolean isSModEffectAPenalty() {
331                // TODO Auto-generated method stub
332                return false;
333        }
334
335        public boolean showInRefitScreenModPickerFor(ShipAPI ship) {
336                return true;
337        }
338
339        @Override
340        public void addRequiredItemSection(TooltipMakerAPI tooltip, FleetMemberAPI member, ShipVariantAPI currentVariant,
341                        MarketAPI dockedAt, float width, boolean isForModSpec) {
342                // TODO Auto-generated method stub
343                
344        }
345
346}