001package com.fs.starfarer.api.impl.campaign;
002
003import java.util.List;
004
005import com.fs.starfarer.api.EveryFrameScript;
006import com.fs.starfarer.api.Global;
007import com.fs.starfarer.api.campaign.CampaignFleetAPI;
008import com.fs.starfarer.api.campaign.FleetAssignment;
009import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
010import com.fs.starfarer.api.campaign.ai.FleetAssignmentDataAPI;
011import com.fs.starfarer.api.campaign.ai.ModularFleetAIAPI;
012import com.fs.starfarer.api.campaign.econ.MarketAPI;
013import com.fs.starfarer.api.campaign.rules.MemoryAPI;
014import com.fs.starfarer.api.impl.campaign.events.BaseEventPlugin.MarketFilter;
015import com.fs.starfarer.api.impl.campaign.ids.Conditions;
016import com.fs.starfarer.api.impl.campaign.ids.Factions;
017import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
018import com.fs.starfarer.api.util.IntervalUtil;
019import com.fs.starfarer.api.util.Misc;
020import com.fs.starfarer.api.util.Misc.FleetFilter;
021
022public class SmugglingScanScript implements EveryFrameScript {
023
024        public static final String SCAN_COMPLETE_KEY = "$smugglingScanComplete";
025        public static final String MARKET_TIMEOUT_KEY = "$smugglingScanTimeout";
026        
027        private IntervalUtil interval = new IntervalUtil(0.1f, 0.3f);
028        
029        private float currDuration = 0f;
030        private float currElapsed = 0f;
031        private CampaignFleetAPI curr = null;
032        
033        public void advance(float amount) {
034                float days = Global.getSector().getClock().convertToDays(amount);
035                
036                if (curr != null) {
037                        maintainOngoingScan(days);
038                        return;
039                }
040                
041                interval.advance(days);
042                if (!interval.intervalElapsed()) return;
043                
044                final float MAX_RANGE_FROM_MARKET = 5000;
045                final float MAX_RANGE_FROM_PLAYER = 2000;
046                
047                final CampaignFleetAPI player = Global.getSector().getPlayerFleet();
048                if (player == null || player.isInHyperspace()) return;
049                final MarketAPI market = Misc.findNearestLocalMarket(player, MAX_RANGE_FROM_MARKET, new MarketFilter() {
050                        public boolean acceptMarket(MarketAPI market) {
051                                if (market.hasCondition(Conditions.FREE_PORT)) return false;
052                                
053                                MemoryAPI mem = market.getMemoryWithoutUpdate();
054                                //mem.unset(MARKET_TIMEOUT_KEY);
055                                if (mem.contains(MARKET_TIMEOUT_KEY)) return false;
056                                return true;
057                        }
058                });
059                if (market == null) return;
060                if (!market.getFaction().getCustomBoolean(Factions.CUSTOM_ALLOWS_TRANSPONDER_OFF_TRADE) && !player.isTransponderOn()) {
061                        return;
062                }
063                
064                if (market.getFaction().isHostileTo(player.getFaction())) return;
065                
066                List<CampaignFleetAPI> patrols = Misc.findNearbyFleets(player, MAX_RANGE_FROM_PLAYER, new FleetFilter() {
067                        public boolean accept(CampaignFleetAPI curr) {
068                                if (curr.getFaction() != market.getFaction()) return false;
069                                if (curr.getFaction().isPlayerFaction()) return false;
070                                if (curr.isHostileTo(player)) return false;
071                                if (curr.isStationMode()) return false;
072                                if (Misc.getSourceMarket(curr) != market) return false;
073                                if (!curr.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PATROL_FLEET)) return false;
074                                if (curr.getAI() instanceof ModularFleetAIAPI) {
075                                        ModularFleetAIAPI ai = (ModularFleetAIAPI) curr.getAI();
076                                        if (ai.isFleeing()) return false;
077                                        if (curr.getInteractionTarget() instanceof CampaignFleetAPI) return false;
078                                }
079                                VisibilityLevel vis = player.getVisibilityLevelTo(curr);
080                                if (vis == VisibilityLevel.NONE) return false;
081                                return true;
082                        }
083                });
084                
085                if (patrols.isEmpty()) return;
086                
087                float minDist = Float.MAX_VALUE;
088                CampaignFleetAPI closestPatrol = null;
089                float closestSuspicion = 0f;
090                for (CampaignFleetAPI curr : patrols) {
091                        float dist = Misc.getDistance(player.getLocation(), curr.getLocation());
092                        float extra = curr.getMemoryWithoutUpdate().getFloat(MemFlags.PATROL_EXTRA_SUSPICION);
093                        if (dist < minDist || extra > closestSuspicion) {
094                                minDist = dist;
095                                closestSuspicion = extra;
096                                closestPatrol = curr;
097                        }
098                }
099                
100                if (closestPatrol == null) return;
101                
102                curr = closestPatrol;
103
104                float threshold = 0.05f;
105                MemoryAPI marketMemory = market.getMemory();
106                float suspicionLevel = marketMemory.getFloat(MemFlags.MEMORY_MARKET_SMUGGLING_SUSPICION_LEVEL);
107                suspicionLevel += closestSuspicion;
108                //suspicionLevel = 1f;
109                boolean doScan = (float) Math.random() < suspicionLevel * 5f && suspicionLevel >= threshold;
110                if (curr.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PATROL_ALLOW_TOFF)) {
111                        doScan = false;
112                }
113                //doScan = true;
114                
115                if (doScan) {
116                        currDuration = 10f + (float) Math.random() * 5f;
117                        currElapsed = 0f;
118                        MemoryAPI mem = curr.getMemoryWithoutUpdate();
119                        Misc.setFlagWithReason(mem, MemFlags.MEMORY_KEY_PURSUE_PLAYER, "smugglingScan", true, 1f);
120                        Misc.setFlagWithReason(mem, MemFlags.MEMORY_KEY_STICK_WITH_PLAYER_IF_ALREADY_TARGET, "smugglingScan", true, currDuration);
121                } else {
122                        curr = null;
123                }
124                
125                
126                if (suspicionLevel >= threshold) {
127                        float timeoutDuration = 20f + (float) Math.random() * 10f;
128                        marketMemory.set(MARKET_TIMEOUT_KEY, true, timeoutDuration);
129                }
130        }
131
132        public void maintainOngoingScan(float days) {
133                if (!curr.isAlive()) {
134                        cleanUpCurr();
135                        return;
136                }
137                if (curr.isHostileTo(Global.getSector().getPlayerFleet())) {
138                        cleanUpCurr();
139                        return;
140                }
141                
142                currElapsed += days;
143                if (currElapsed > currDuration) {
144                        cleanUpCurr();
145                        return;
146                }
147                
148
149                // if visible, keep extending "pursue player" duration by a day
150                // so, player has to lose the patrol for a day to be able to sneak into market
151                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
152                if (player.isInHyperspace() || player.isInHyperspaceTransition()) {
153                        cleanUpCurr();
154                        return;
155                }
156                
157                VisibilityLevel vis = player.getVisibilityLevelTo(curr);
158                
159                if (vis != VisibilityLevel.NONE) {
160                        MemoryAPI mem = curr.getMemoryWithoutUpdate();
161                        if (mem.getBoolean(SCAN_COMPLETE_KEY)) { // this happens when fleet interacts w/ player
162                                cleanUpCurr();
163                                return;
164                        }
165                        //curr.getMemoryWithoutUpdate().contains("$pursuePlayer_smugglingScan");
166                        if (!curr.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PATROL_ALLOW_TOFF)) {
167                                Misc.setFlagWithReason(mem, MemFlags.MEMORY_KEY_PURSUE_PLAYER, "smugglingScan", true, 1f);
168                        } else {
169                                Misc.setFlagWithReason(mem, MemFlags.MEMORY_KEY_PURSUE_PLAYER, "smugglingScan", false, 0f);
170                        }
171                }
172        }
173        
174        
175        protected void cleanUpCurr() {
176                if (curr != null) {
177                        CampaignFleetAPI pf = Global.getSector().getPlayerFleet();
178                        FleetAssignmentDataAPI a = curr.getCurrentAssignment();
179                        if (a != null && a.getAssignment() == FleetAssignment.INTERCEPT &&
180                                        a.getTarget() == pf) {
181                                curr.removeFirstAssignmentIfItIs(a.getAssignment());
182                        }
183                        curr.setInteractionTarget(null);
184                        if (curr.getAI() instanceof ModularFleetAIAPI) {
185                                ModularFleetAIAPI ai = (ModularFleetAIAPI) curr.getAI();
186                                if (ai.getTacticalModule().getTarget() == pf) {
187                                        ai.getTacticalModule().setTarget(null);
188                                }
189                        }
190                        
191                        MemoryAPI mem = curr.getMemoryWithoutUpdate();
192                        Misc.setFlagWithReason(mem, MemFlags.MEMORY_KEY_PURSUE_PLAYER, "smugglingScan", false, 0f);
193                        Misc.setFlagWithReason(mem, MemFlags.MEMORY_KEY_STICK_WITH_PLAYER_IF_ALREADY_TARGET, "smugglingScan", false, 0f);
194                        mem.unset(SCAN_COMPLETE_KEY);
195                        curr = null;
196                        currDuration = currElapsed = 0f;
197                }
198        }
199        
200
201        public boolean isDone() {
202                return false;
203        }
204
205        public boolean runWhilePaused() {
206                return false;
207        }
208}
209
210
211
212
213
214
215
216
217
218
219
220
221