001package com.fs.starfarer.api.impl.campaign;
002
003import java.util.Random;
004
005import org.lwjgl.util.vector.Vector2f;
006
007import com.fs.starfarer.api.campaign.CampaignFleetAPI;
008import com.fs.starfarer.api.campaign.FleetAssignment;
009import com.fs.starfarer.api.campaign.SectorEntityToken;
010import com.fs.starfarer.api.campaign.TextPanelAPI;
011import com.fs.starfarer.api.campaign.ai.FleetAssignmentDataAPI;
012import com.fs.starfarer.api.campaign.rules.MemoryAPI;
013import com.fs.starfarer.api.combat.MutableStat.StatMod;
014import com.fs.starfarer.api.impl.campaign.ghosts.BaseSensorGhost;
015import com.fs.starfarer.api.impl.campaign.ghosts.GBGoInDirection;
016import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
017import com.fs.starfarer.api.impl.campaign.ids.Tags;
018import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
019import com.fs.starfarer.api.ui.TooltipMakerAPI;
020import com.fs.starfarer.api.util.Misc;
021
022public class SensorArrayEntityPlugin extends BaseCampaignObjectivePlugin {
023
024        public static float SENSOR_BONUS = 700f;
025        public static float SENSOR_BONUS_MAKESHIFT = 400f;
026        //public static float SENSOR_PENALTY_MULT_FROM_HACK = 0.75f;
027        
028        public void init(SectorEntityToken entity, Object pluginParams) {
029                super.init(entity, pluginParams);
030                readResolve();
031        }
032        
033        Object readResolve() {
034                return this;
035        }
036        
037        public void advance(float amount) {
038                if (entity.getContainingLocation() == null || entity.isInHyperspace()) return;
039                //if (isReset()) return;
040                boolean reset = isReset();
041                
042                String id = getModId();
043                for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) {
044                        if (fleet.isInHyperspaceTransition()) continue;
045                        
046                        if (fleet.getFaction() == entity.getFaction() || (isHacked() && fleet.getFaction().isPlayerFaction())) {
047                                if (reset && !fleet.getFaction().isPlayerFaction()) {
048                                        respondToFalseSensorReadings(fleet);
049                                } else if (reset && fleet.isPlayerFleet()) {
050                                        spawnPlayerSensorReading(fleet);
051                                }
052                                
053                                String desc = "Sensor array";
054                                float bonus = SENSOR_BONUS;
055                                if (isMakeshift()) {
056                                        desc = "Makeshift sensor array";
057                                        bonus = SENSOR_BONUS_MAKESHIFT;
058                                }
059                                
060//                              if (fleet.getFaction() == entity.getFaction() && isHacked() && !entity.getFaction().isPlayerFaction()) {
061//                                      fleet.getStats().addTemporaryModMult(0.1f, id,
062//                                                      desc, SENSOR_PENALTY_MULT_FROM_HACK, 
063//                                                      fleet.getStats().getSensorRangeMod());
064//                              }
065                                
066                                StatMod curr = fleet.getStats().getSensorRangeMod().getFlatBonus(id);
067                                if (curr == null || curr.value <= bonus) {
068                                        fleet.getStats().addTemporaryModFlat(0.1f, id,
069                                                        desc, bonus, 
070                                                        fleet.getStats().getSensorRangeMod());
071                                }
072                        }
073                }
074                
075        }
076        
077        protected boolean isMakeshift() {
078                return entity.hasTag(Tags.MAKESHIFT);
079        }
080        
081        public void printEffect(TooltipMakerAPI text, float pad) {
082                int bonus = (int) SENSOR_BONUS;
083                if (isMakeshift()) {
084                        bonus = (int) SENSOR_BONUS_MAKESHIFT;
085                }
086                
087                text.addPara(BaseIntelPlugin.INDENT + "%s sensor range for all same-faction fleets in system",
088                                pad, Misc.getHighlightColor(), "+" + bonus);
089                if (isReset()) {
090                        //text.addPara(BaseIntelPlugin.INDENT + "Auto-calibrating after factory reset; non-functional", 3f);
091                        text.addPara(BaseIntelPlugin.INDENT + "Generating false readings", 3f);
092                }
093                
094//              text.addPara(BaseIntelPlugin.INDENT + "%s sensor range to same-faction fleets when hacked",
095//                              0f, Misc.getHighlightColor(), "-" + (int) Math.round((1f - SENSOR_PENALTY_MULT_FROM_HACK) * 100f) + "%");
096        }
097
098        public void printNonFunctionalAndHackDescription(TextPanelAPI text) {
099                if (entity.getMemoryWithoutUpdate().getBoolean(MemFlags.OBJECTIVE_NON_FUNCTIONAL)) {
100                        text.addPara("This one, however, does not appear to be transmitting a sensor telemetry broadcast. The cause of its lack of function is unknown.");
101                }
102                if (isHacked()) {
103                        text.addPara("You have a hack running on this sensor array.");
104                }
105//              if (isReset()) {
106//                      text.addPara("This sensor array is auto-calibrating after a factory reset and is effectively non-functional.");
107//              }
108        }
109        
110        
111        
112        @Override
113        public void addHackStatusToTooltip(TooltipMakerAPI text, float pad) {
114                int bonus = (int) SENSOR_BONUS;
115                if (isMakeshift()) {
116                        bonus = (int) SENSOR_BONUS_MAKESHIFT;
117                }
118                text.addPara("%s sensor range for in-system fleets",
119                                pad, Misc.getHighlightColor(), "+" + bonus);
120                
121//              text.addPara("%s%% sensor range when hacked",
122//                              pad, Misc.getHighlightColor(), "-" + (int) Math.round((1f - SENSOR_PENALTY_MULT_FROM_HACK) * 100f));
123                
124                super.addHackStatusToTooltip(text, pad);
125        }
126
127        protected String getModId() {
128                return "sensor_array";
129        }
130        
131
132        public static String GHOST_RESPONSE = "ghost_response"; // custom value added to assignments so we know which to clear
133
134        protected void spawnPlayerSensorReading(CampaignFleetAPI fleet) {
135                Random random = Misc.random;
136                
137                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
138                if (mem.getBoolean(MemFlags.FLEET_NOT_CHASING_GHOST)) {
139                        return;
140                }
141                if (mem.getBoolean(MemFlags.FLEET_CHASING_GHOST)) {
142                        return;
143                }
144                boolean spawnReading = random.nextFloat() < 0.5f;
145                //spawnReading = true;
146                if (!spawnReading) {
147                        mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, 1f + 2f * random.nextFloat());
148                        return;
149                }
150                
151                float dur = 3f + 3f + random.nextFloat();
152                mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, dur * 0.5f);
153                
154                BaseSensorGhost g = new BaseSensorGhost(null, 0);
155                
156                float r = random.nextFloat();
157                int maxBurn;
158                if (r < 0.25f) {
159                        g.initEntity(g.genMediumSensorProfile(), g.genSmallRadius(), 0, fleet.getContainingLocation());
160                        maxBurn = 9 + random.nextInt(3);
161                } else if (r < 0.6f) {
162                        g.initEntity(g.genLargeSensorProfile(), g.genMediumRadius(), 0, fleet.getContainingLocation());
163                        maxBurn = 8 + random.nextInt(3);
164                } else {
165                        g.initEntity(g.genLargeSensorProfile(), g.genLargeRadius(), 0, fleet.getContainingLocation());
166                        maxBurn = 7 + random.nextInt(3);
167                }
168                
169                
170                if (!g.placeNearPlayer()) {
171                        return;
172                }
173                //g.setDespawnRange(200f);
174                
175                
176                float speed = Misc.getSpeedForBurnLevel(maxBurn);
177                float accelMult = speed / Misc.getSpeedForBurnLevel(20f);
178                if (accelMult < 0.1f) accelMult = 0.1f;
179                g.setAccelMult(1f/ accelMult);
180                
181                float dir = Misc.getAngleInDegrees(g.getEntity().getLocation(), fleet.getLocation());
182                float sign = Math.signum(random.nextFloat() - 0.5f);
183                dir += sign * (30f + random.nextFloat() * 60f);
184                
185                
186                g.addBehavior(new GBGoInDirection(dur, dir, maxBurn));
187                
188                fleet.getContainingLocation().addScript(g);
189                
190        }
191        
192        protected void respondToFalseSensorReadings(CampaignFleetAPI fleet) {
193                if (fleet.isStationMode()) return;
194                if (fleet.getAI() == null) {
195                        return;
196                }
197                if (fleet.getAI().getAssignmentsCopy() == null) {
198                        return;
199                }
200                
201                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
202                if (mem.getBoolean(MemFlags.FLEET_NOT_CHASING_GHOST)) {
203                        return;
204                }
205                if (mem.getBoolean(MemFlags.FLEET_CHASING_GHOST)) {
206                        return;
207                }
208                if (mem.getBoolean(MemFlags.FLEET_BUSY)) {
209                        return;
210                }
211                boolean patrol = mem.getBoolean(MemFlags.MEMORY_KEY_PATROL_FLEET);
212                boolean warFleet = mem.getBoolean(MemFlags.MEMORY_KEY_WAR_FLEET);
213                boolean pirate = mem.getBoolean(MemFlags.MEMORY_KEY_PIRATE);
214                if (!patrol && !warFleet && !pirate) {
215                        return;
216                }
217                
218                Random random = (Random) mem.get(MemFlags.FLEET_CHASING_GHOST_RANDOM);
219                if (random == null) {
220                        random = Misc.getRandom(Misc.getSalvageSeed(fleet), 7);
221                        mem.set(MemFlags.FLEET_CHASING_GHOST_RANDOM, random, 30f);
222                }
223                
224                boolean willRespond = random.nextFloat() < 0.75f;
225                //willRespond = true;
226                if (!willRespond) {
227                        mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, 1f + 1f * random.nextFloat());
228                        Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 
229                                           MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, false, 0f);
230                        for (FleetAssignmentDataAPI curr : fleet.getAI().getAssignmentsCopy()) {
231                                if (GHOST_RESPONSE.equals(curr.getCustom())) {
232                                        fleet.getAI().removeAssignment(curr);
233                                }
234                        }
235                        return;
236                }
237                
238                float chaseDur = (2.5f + (float) Math.random()) * 2f;
239                Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 
240                                MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, true, chaseDur);
241                mem.set(MemFlags.FLEET_BUSY, true, chaseDur);
242                mem.set(MemFlags.FLEET_NOT_CHASING_GHOST, true, chaseDur + 8f + 4f * random.nextFloat());
243                
244                float angle = Misc.getAngleInDegrees(fleet.getLocation()); // away from center of system;
245                float arc = 270f;
246                angle += arc/2f - arc * random.nextFloat();
247                float dist = 3000f + 3000f * random.nextFloat();
248                Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle);
249                loc.scale(dist);
250                Vector2f.add(loc, fleet.getLocation(), loc);
251                
252
253                String actionText = "investigating anomalous sensor reading";
254                
255                SectorEntityToken target = fleet.getContainingLocation().createToken(loc);
256                fleet.addAssignmentAtStart(FleetAssignment.PATROL_SYSTEM, target, 3f, actionText, null);
257                FleetAssignmentDataAPI curr = fleet.getCurrentAssignment();
258                if (curr != null) {
259                        curr.setCustom(GHOST_RESPONSE);
260                }
261                
262                if (dist > 2000f) {
263                        fleet.addAssignmentAtStart(FleetAssignment.GO_TO_LOCATION, target, 3f, actionText, null);
264                        curr = fleet.getCurrentAssignment();
265                        if (curr != null) {
266                                curr.setCustom(GHOST_RESPONSE);
267                        }
268                }
269        }
270        
271//      protected void unrespond(CampaignFleetAPI fleet) {
272//              Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 
273//                                                         MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, false, 0f);
274//              for (FleetAssignmentDataAPI curr : fleet.getAI().getAssignmentsCopy()) {
275//                      if (GHOST_RESPONSE.equals(curr.getCustom())) {
276//                              fleet.getAI().removeAssignment(curr);
277//                      }
278//              }
279//      }
280//      
281//      protected void respond(CampaignFleetAPI fleet) {
282//              unrespond(fleet);
283//              
284//              Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 
285//                                                              MemFlags.FLEET_CHASING_GHOST, GHOST_RESPONSE, true, (1.5f + (float) Math.random()) * 0.2f);
286//              
287//              fleet.addAssignmentAtStart(FleetAssignment.PATROL_SYSTEM, params.target, 3f, params.actionText, null);
288//              FleetAssignmentDataAPI curr = fleet.getCurrentAssignment();
289//              if (curr != null) {
290//                      curr.setCustom(GHOST_RESPONSE);
291//              }
292//              
293//              float dist = Misc.getDistance(params.target, fleet);
294//              if (dist > 2000f) {
295//                      fleet.addAssignmentAtStart(FleetAssignment.GO_TO_LOCATION, params.target, 3f, params.travelText, null);
296//                      //fleet.addAssignmentAtStart(FleetAssignment.DELIVER_CREW, params.target, 3f, params.travelText, null);
297//                      curr = fleet.getCurrentAssignment();
298//                      if (curr != null) {
299//                              curr.setCustom(GHOST_RESPONSE);
300//                      }
301//              }
302//              
303//              //Global.getSector().addPing(fleet, Pings.DANGER);
304//      }
305}
306
307
308