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