001package com.fs.starfarer.api.impl.campaign.intel;
002
003import java.awt.Color;
004import java.util.List;
005import java.util.Set;
006
007import org.apache.log4j.Logger;
008
009import com.fs.starfarer.api.EveryFrameScript;
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.BattleAPI;
012import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
013import com.fs.starfarer.api.campaign.CampaignFleetAPI;
014import com.fs.starfarer.api.campaign.FactionAPI;
015import com.fs.starfarer.api.campaign.LocationAPI;
016import com.fs.starfarer.api.campaign.ReputationActionResponsePlugin.ReputationAdjustmentResult;
017import com.fs.starfarer.api.campaign.SectorEntityToken;
018import com.fs.starfarer.api.campaign.StarSystemAPI;
019import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.ActionType;
020import com.fs.starfarer.api.campaign.econ.MarketAPI;
021import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
022import com.fs.starfarer.api.fleet.FleetMemberAPI;
023import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin;
024import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
025import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
026import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript;
027import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript.MilitaryResponseParams;
028import com.fs.starfarer.api.impl.campaign.econ.BaseMarketConditionPlugin;
029import com.fs.starfarer.api.impl.campaign.ids.Factions;
030import com.fs.starfarer.api.impl.campaign.ids.Tags;
031import com.fs.starfarer.api.ui.SectorMapAPI;
032import com.fs.starfarer.api.ui.TooltipMakerAPI;
033import com.fs.starfarer.api.util.Misc;
034
035public class SystemBountyIntel extends BaseIntelPlugin implements EveryFrameScript, FleetEventListener {
036        public static Logger log = Global.getLogger(SystemBountyIntel.class);
037
038        public static float MAX_DURATION = 60;
039
040        protected MarketAPI market;
041        protected LocationAPI location;
042        protected float elapsedDays = 0f;
043        protected float duration = MAX_DURATION;
044        
045        protected float baseBounty = 0;
046        
047        protected FactionAPI faction = null;
048        protected FactionAPI enemyFaction = null;
049        protected SystemBountyResult latestResult;
050        
051        protected boolean commerceMode = false; // due to Commerce industry in player system
052
053        protected MilitaryResponseScript script;
054        
055        public SystemBountyIntel(MarketAPI market) {
056                this(market, -1, false);
057        }
058        public SystemBountyIntel(MarketAPI market, int baseReward, boolean commerceMode) {
059                this.market = market;
060                this.commerceMode = commerceMode;
061                
062                location = market.getContainingLocation();
063                faction = market.getFaction();
064                if (commerceMode) {
065                        faction = Global.getSector().getFaction(Factions.INDEPENDENT);
066                }
067                
068                if (!commerceMode && market.getFaction().isPlayerFaction()) {
069                        endImmediately();
070                        return;
071                }
072                
073                baseBounty = Global.getSettings().getFloat("baseSystemBounty");
074                float marketSize = market.getSize();
075                
076                baseBounty *= (marketSize + 5f) / 10f;
077                
078                //float lowStabilityMult = BaseMarketConditionPlugin.getLowStabilityPenaltyMult(market);
079                float highStabilityMult = BaseMarketConditionPlugin.getHighStabilityBonusMult(market);
080                highStabilityMult = 1f + (highStabilityMult - 1f) * 0.5f;
081                
082                //baseBounty *= lowStabilityMult;
083                baseBounty *= highStabilityMult;
084                
085                baseBounty = (int) baseBounty;
086                if (baseReward > 0) {
087                        baseBounty = baseReward;
088                }
089                
090                log.info(String.format("Starting bounty at market [%s], %d credits per frigate", market.getName(), (int) baseBounty));
091                
092                updateLikelyCauseFaction();
093                
094                
095                //conditionToken = market.addCondition(Conditions.EVENT_SYSTEM_BOUNTY, this);
096                
097                if (commerceMode) {
098                        Global.getSector().getIntelManager().addIntel(this);
099                } else {
100                        Global.getSector().getIntelManager().queueIntel(this);
101                }
102                
103                Global.getSector().getListenerManager().addListener(this);
104                
105                if (!commerceMode) {
106                        MilitaryResponseParams params = new MilitaryResponseParams(ActionType.HOSTILE, 
107                                        "system_bounty_" + market.getId(), 
108                                        getFactionForUIColors(),
109                                        market.getPrimaryEntity(),
110                                        0.75f,
111                                        duration);
112                        script = new MilitaryResponseScript(params);
113                        location.addScript(script);
114                }
115        }
116        
117        public void reportMadeVisibleToPlayer() {
118                if (!isEnding() && !isEnded()) {
119                        duration = Math.max(duration * 0.5f, Math.min(duration * 2f, MAX_DURATION));
120                }
121        }
122        
123        public float getElapsedDays() {
124                return elapsedDays;
125        }
126
127        public void setElapsedDays(float elapsedDays) {
128                this.elapsedDays = elapsedDays;
129        }
130        
131        public void reset() {
132                elapsedDays = 0f;
133                endingTimeRemaining = null;
134                ending = null;
135                ended = null;
136                script.setElapsed(0f);
137                if (!Global.getSector().getListenerManager().hasListener(this)) {
138                        Global.getSector().getListenerManager().addListener(this);
139                }
140        }
141
142
143
144        public MarketAPI getMarket() {
145                return market;
146        }
147        
148        
149        private void updateLikelyCauseFaction() {
150                int maxSize = 0;
151                MarketAPI maxOther = null;
152                for (MarketAPI other : Misc.getNearbyMarkets(market.getLocationInHyperspace(), 0f)) {
153                        if (market.getFaction() == other.getFaction()) continue;
154                        if (!market.getFaction().isHostileTo(other.getFaction())) continue;
155                        
156                        int size = other.getSize();
157                        if (size > maxSize) {
158                                maxSize = size;
159                                maxOther = other;
160                        }
161                }
162                
163                if (maxOther != null) {
164                        enemyFaction = maxOther.getFaction();
165                } else {
166                        enemyFaction = Global.getSector().getFaction(Factions.PIRATES);
167                }
168                
169        }
170        
171        @Override
172        protected void advanceImpl(float amount) {
173                float days = Global.getSector().getClock().convertToDays(amount);
174                
175                elapsedDays += days;
176
177                if (elapsedDays >= duration && !isDone() && !commerceMode) {
178                        endAfterDelay();
179                        boolean current = market.getContainingLocation() == Global.getSector().getCurrentLocation();
180                        sendUpdateIfPlayerHasIntel(new Object(), !current);
181                        return;
182                }
183                if (faction != market.getFaction() || !market.isInEconomy()) {
184                        endAfterDelay();
185                        boolean current = market.getContainingLocation() == Global.getSector().getCurrentLocation();
186                        sendUpdateIfPlayerHasIntel(new Object(), !current);
187                        return;
188                }
189        }
190        
191        public float getTimeRemainingFraction() {
192                if (commerceMode) return 1f;
193                float f = 1f - elapsedDays / duration;
194                return f;
195        }
196        
197        
198
199        @Override
200        protected void notifyEnding() {
201                super.notifyEnding();
202                log.info(String.format("Ending bounty at market [%s]", market.getName()));
203                
204                Global.getSector().getListenerManager().removeListener(this);
205                
206                location.removeScript(script);
207                
208                //market.removeSpecificCondition(conditionToken);
209        }
210        
211        
212        public static class SystemBountyResult {
213                public int payment;
214                public float fraction;
215                public ReputationAdjustmentResult rep;
216                public SystemBountyResult(int payment, float fraction, ReputationAdjustmentResult rep) {
217                        this.payment = payment;
218                        this.fraction = fraction;
219                        this.rep = rep;
220                }
221                
222        }
223
224        public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
225        }
226
227        public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
228                if (isEnded() || isEnding()) return;
229                
230                if (!battle.isPlayerInvolved()) return;
231                
232                if (!Misc.isNear(primaryWinner, market.getLocationInHyperspace())) return;
233                
234                int payment = 0;
235                float fpDestroyed = 0;
236                for (CampaignFleetAPI otherFleet : battle.getNonPlayerSideSnapshot()) {
237                        if (commerceMode) {
238                                if (!market.getFaction().isHostileTo(otherFleet.getFaction()) && 
239                                                !otherFleet.getFaction().isHostileTo(Factions.INDEPENDENT)) continue;
240                                
241                                if (Misc.isTrader(otherFleet)) continue;
242                                if (Factions.INDEPENDENT.equals(otherFleet.getFaction().getId())) continue;
243                        } else {
244                                if (!market.getFaction().isHostileTo(otherFleet.getFaction())) continue;
245                        }
246                        
247                        float bounty = 0;
248                        for (FleetMemberAPI loss : Misc.getSnapshotMembersLost(otherFleet)) {
249                                float mult = Misc.getSizeNum(loss.getHullSpec().getHullSize());
250                                bounty += mult * baseBounty;
251                                fpDestroyed += loss.getFleetPointCost();
252                        }
253                        
254                        payment += (int) (bounty * battle.getPlayerInvolvementFraction());
255                }
256        
257                if (payment > 0) {
258                        Global.getSector().getPlayerFleet().getCargo().getCredits().add(payment);
259                        
260                        float repFP = (int)(fpDestroyed * battle.getPlayerInvolvementFraction());
261                        ReputationAdjustmentResult rep = Global.getSector().adjustPlayerReputation(
262                                                        new RepActionEnvelope(RepActions.SYSTEM_BOUNTY_REWARD, new Float(repFP), null, null, true, false), 
263                                                        market.getFaction().getId());
264                        latestResult = new SystemBountyResult(payment, battle.getPlayerInvolvementFraction(), rep);
265                        sendUpdateIfPlayerHasIntel(latestResult, false);
266                }
267        }
268        
269        public boolean runWhilePaused() {
270                return false;
271        }
272        protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) {
273                
274                Color h = Misc.getHighlightColor();
275                Color g = Misc.getGrayColor();
276                float pad = 3f;
277                float opad = 10f;
278                
279                float initPad = pad;
280                if (mode == ListInfoMode.IN_DESC) initPad = opad;
281                
282                Color tc = getBulletColorForMode(mode);
283                
284                bullet(info);
285                boolean isUpdate = getListInfoParam() != null;
286                
287                if (isEnding() && isUpdate) {
288                        //info.addPara("Over", initPad);
289                } else {
290                        if (isUpdate && latestResult != null) {
291                                info.addPara("%s received", initPad, tc, h, Misc.getDGSCredits(latestResult.payment));
292                                if (Math.round(latestResult.fraction * 100f) < 100f) {
293                                        info.addPara("%s share based on damage dealt", 0f, tc, h, 
294                                                        "" + (int) Math.round(latestResult.fraction * 100f) + "%");
295                                }
296                                CoreReputationPlugin.addAdjustmentMessage(latestResult.rep.delta, faction, null, 
297                                                                                                                  null, null, info, tc, isUpdate, 0f);
298                        } else if (mode == ListInfoMode.IN_DESC) {
299                                info.addPara("%s base reward per frigate", initPad, tc, h, Misc.getDGSCredits(baseBounty));
300                                if (!commerceMode) {
301                                        addDays(info, "remaining", duration - elapsedDays, tc);
302                                }
303                        } else {
304                                if (!isEnding()) {
305                                        info.addPara("Faction: " + faction.getDisplayName(), initPad, tc,
306                                                                 faction.getBaseUIColor(), faction.getDisplayName());
307                                        info.addPara("%s base reward per frigate", 0f, tc, h, Misc.getDGSCredits(baseBounty));
308                                        if (!commerceMode) {
309                                                addDays(info, "remaining", duration - elapsedDays, tc);
310                                        }
311                                }
312                        }
313                }
314                unindent(info);
315        }
316        
317        @Override
318        public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) {
319                Color h = Misc.getHighlightColor();
320                Color g = Misc.getGrayColor();
321                Color c = getTitleColor(mode);
322                float pad = 3f;
323                float opad = 10f;
324                
325                info.addPara(getName(), c, 0f);
326                
327                addBulletPoints(info, mode);
328        }
329        
330        public String getSortString() {
331                return "System Bounty";
332        }
333        
334        public String getName() {
335                String name = market.getName();
336                StarSystemAPI system = market.getStarSystem();
337                if (system != null) {
338                        name = system.getBaseName();
339                }
340                if (isEnding()) {
341                        return "Bounty Ended - " + name;
342                }
343                return "System Bounty - " + name;
344        }
345        
346        @Override
347        public FactionAPI getFactionForUIColors() {
348                return faction;
349        }
350
351        public String getSmallDescriptionTitle() {
352                return getName();
353                //return null;
354        }
355        
356        public void createSmallDescription(TooltipMakerAPI info, float width, float height) {
357                createSmallDescription(info, width, height, false);
358        }
359        public void createSmallDescription(TooltipMakerAPI info, float width, float height, 
360                                                                           boolean forMarketConditionTooltip) {
361                Color h = Misc.getHighlightColor();
362                Color g = Misc.getGrayColor();
363                Color tc = Misc.getTextColor();
364                float pad = 3f;
365                float opad = 10f;
366
367                //info.addPara(getName(), c, 0f);
368                
369                //info.addSectionHeading(getName(), Alignment.MID, 0f);
370                
371                if (!forMarketConditionTooltip) {
372                        info.addImage(faction.getLogo(), width, 128, opad);
373                }
374
375                String locStr = "near " + market.getName();
376                if (market.getStarSystem() != null) {
377                        locStr = "in or near the " + market.getStarSystem().getNameWithLowercaseType();
378                }
379                
380                if (commerceMode) {
381                        info.addPara("%s commercial concerns " + market.getOnOrAt() + " " + market.getName() + 
382                                        " have banded together and posted a modest but long-term bounty on all "
383                                        + "hostile fleets " + locStr + ".",
384                                        opad, faction.getBaseUIColor(), Misc.ucFirst(faction.getPersonNamePrefix()));
385                        info.addPara("The bounty stipulates that trade fleets are an exception - "
386                                        + "attacking them will not result in a reward, regardless of their faction.", opad);
387                } else {
388                        info.addPara("%s authorities " + market.getOnOrAt() + " " + market.getName() + 
389                                        " have posted a bounty on all hostile fleets " + locStr + ".",
390                                        opad, faction.getBaseUIColor(), Misc.ucFirst(faction.getPersonNamePrefix()));
391                }
392
393                if (isEnding()) {
394                        info.addPara("This bounty is no longer on offer.", opad);
395                        return;
396                }
397                
398//              if (!Global.getSector().getListenerManager().hasListener(this)) {
399//                      Global.getSector().getListenerManager().addListener(this);
400//                      info.addPara("Listener not registered!", opad);
401//              }
402                
403                addBulletPoints(info, ListInfoMode.IN_DESC);
404                
405                if (!commerceMode) {
406                        if (enemyFaction != null) {
407                                info.addPara("Likely triggered by %s activity.",
408                                                opad, enemyFaction.getBaseUIColor(), enemyFaction.getPersonNamePrefix());
409                        }
410                } else {
411                        info.addPara("Triggered by the presence of Commerce on one of your colonies in-system, and by the "
412                                        + "level of hostile activity.", opad);
413                }
414                
415                
416                info.addPara("Payment depends on the number and size of ships destroyed. " +
417                                         "Standing with " + faction.getDisplayNameWithArticle() + " may also improve.",
418                                         opad);
419//                                       opad, faction.getBaseUIColor(),
420//                                       faction.getDisplayNameWithArticleWithoutArticle());
421                
422                
423                if (!commerceMode) {
424                        String isOrAre = faction.getDisplayNameIsOrAre();
425                        FactionCommissionIntel temp = new FactionCommissionIntel(faction);
426                        List<FactionAPI> hostile = temp.getHostileFactions();
427                        if (hostile.isEmpty()) {
428                                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + isOrAre + " not currently hostile to any major factions.", 0f);
429                        } else {
430                                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + isOrAre + " currently hostile to:", opad);
431                                
432                                info.setParaFontDefault();
433                                
434                                info.setBulletedListMode(BaseIntelPlugin.INDENT);
435                                float initPad = pad;
436                                for (FactionAPI other : hostile) {
437                                        info.addPara(Misc.ucFirst(other.getDisplayName()), other.getBaseUIColor(), initPad);
438                                        initPad = 0f;
439                                }
440                                info.setBulletedListMode(null);
441                        }
442                }
443                
444                if (latestResult != null) {
445                        //Color color = faction.getBaseUIColor();
446                        //Color dark = faction.getDarkUIColor();
447                        //info.addSectionHeading("Most Recent Reward", color, dark, Alignment.MID, opad);
448                        info.addPara("Most recent bounty:", opad);
449                        bullet(info);
450                        info.addPara("%s received", pad, tc, h, Misc.getDGSCredits(latestResult.payment));
451                        if (Math.round(latestResult.fraction * 100f) < 100f) {
452                                info.addPara("%s share based on damage dealt", 0f, tc, h, 
453                                                "" + (int) Math.round(latestResult.fraction * 100f) + "%");
454                        }
455                        CoreReputationPlugin.addAdjustmentMessage(latestResult.rep.delta, faction, null, 
456                                                                                                          null, null, info, tc, false, 0f);
457                        unindent(info);
458                }
459
460        }
461        
462        public String getIcon() {
463                return Global.getSettings().getSpriteName("intel", "system_bounty");
464        }
465        
466        public Set<String> getIntelTags(SectorMapAPI map) {
467                Set<String> tags = super.getIntelTags(map);
468                tags.add(Tags.INTEL_BOUNTY);
469                tags.add(faction.getId());
470                return tags;
471        }
472
473        @Override
474        public SectorEntityToken getMapLocation(SectorMapAPI map) {
475                return market.getPrimaryEntity();
476        }
477
478        
479        public float getDuration() {
480                return duration;
481        }
482
483        public void setDuration(float duration) {
484                this.duration = duration;
485        }
486
487        public float getBaseBounty() {
488                return baseBounty;
489        }
490
491        public void setBaseBounty(float baseBounty) {
492                this.baseBounty = baseBounty;
493        }
494        public boolean isCommerceMode() {
495                return commerceMode;
496        }
497        
498        public void setCommerceMode(boolean commerceMode) {
499                this.commerceMode = commerceMode;
500        }
501        public LocationAPI getLocation() {
502                return location;
503        }
504        
505}
506
507