001package com.fs.starfarer.api.impl.campaign.ghosts;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Random;
006
007import org.lwjgl.util.vector.Vector2f;
008
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.campaign.CampaignFleetAPI;
011import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI;
012import com.fs.starfarer.api.campaign.LocationAPI;
013import com.fs.starfarer.api.campaign.SectorEntityToken;
014import com.fs.starfarer.api.impl.campaign.abilities.InterdictionPulseAbility;
015import com.fs.starfarer.api.impl.campaign.ids.Entities;
016import com.fs.starfarer.api.impl.campaign.ids.Factions;
017import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
018import com.fs.starfarer.api.impl.campaign.ids.Tags;
019import com.fs.starfarer.api.util.Misc;
020import com.fs.starfarer.api.util.SmoothMovementUtil;
021
022public class BaseSensorGhost implements SensorGhost {
023        
024        public static enum DespawnReason {
025                FLEET_IN_RANGE,
026                SCRIPT_ENDED,
027        }
028        
029        protected CustomCampaignEntityAPI entity;
030        protected float despawnRange = 100f;
031        protected boolean despawnOutsideSector = true;
032        protected boolean despawnInAbyss = true;
033        protected boolean fleeing = false;
034        protected int fleeBurnLevel = 30;
035        protected float accelMult = 1f;
036        protected transient boolean creationFailed = false;
037        
038        protected SmoothMovementUtil movement = new SmoothMovementUtil();
039        
040        protected List<GhostBehavior> script = new ArrayList<GhostBehavior>();
041        protected SensorGhostManager manager;
042        
043        public BaseSensorGhost(SensorGhostManager manager, int fleeBurnLevel) {
044                this.manager = manager;
045                this.fleeBurnLevel = fleeBurnLevel;
046        }
047
048        protected Object readResolve() {
049                if (movement == null) {
050                        movement = new SmoothMovementUtil();
051                }
052                return this;
053        }
054        
055        public void addBehavior(GhostBehavior b) {
056                script.add(b);
057        }
058        
059        public void addInterrupt(GhostBehaviorInterrupt interrupt) {
060                if (script.isEmpty()) return;
061                script.get(script.size() - 1).addInterrupt(interrupt);
062        }
063        
064        public float getDespawnRange() {
065                return despawnRange;
066        }
067
068        public void setDespawnRange(float despawnRange) {
069                this.despawnRange = despawnRange;
070        }
071
072        public Random getRandom() {
073                Random random = Misc.random;
074                if (manager != null) random = manager.getRandom();
075                return random;
076        }
077        
078        public float genSmallSensorProfile() {
079                return 700f + getRandom().nextFloat() * 300f;
080        }
081        public float genMediumSensorProfile() {
082                return 1000f + getRandom().nextFloat() * 500f;
083        }
084        public float genLargeSensorProfile() {
085                return 1500f + getRandom().nextFloat() * 500f;
086        }
087        
088        public float genHugeSensorProfile() {
089                return 2500f + getRandom().nextFloat() * 1000f;
090        }
091        
092        public float genTinyRadius() {
093                return 10f + getRandom().nextFloat() * 5f;
094        }
095        public float genVerySmallRadius() {
096                return 20f + getRandom().nextFloat() * 10f;
097        }
098        public float genSmallRadius() {
099                return 22f + getRandom().nextFloat() * 28f;
100        }
101        public float genMediumRadius() {
102                return 50f + getRandom().nextFloat() * 25f;
103        }
104        public float genLargeRadius() {
105                return 75f + getRandom().nextFloat() * 25f;
106        }
107        
108        public float genFloat(float min, float max) {
109                return min + (max - min) * getRandom().nextFloat(); 
110        }
111        public float genInt(int min, int max) {
112                return min + getRandom().nextInt(max - min + 1); 
113        }
114//      public float genRadius(float min, float max) {
115//              return min + (max - min) * getRandom().nextFloat(); 
116//      }
117//      public int genBurn(int min, int max) {
118//              return min + getRandom().nextInt(max - min + 1); 
119//      }
120        public float genDelay(float base) {
121                return base * (0.75f + 0.5f * getRandom().nextFloat()); 
122        }
123        
124        public boolean placeNearPlayer() {
125                return placeNearPlayer(1400f, 2200f); // 2000 is max range at which sensor ping plays
126                //placeNearPlayer(1000f, 1500f);
127        }
128        public boolean placeNearPlayer(float minDist, float maxDist) {
129                CampaignFleetAPI pf = Global.getSector().getPlayerFleet();
130                
131                Random random = getRandom();
132                Vector2f loc = new Vector2f();
133                
134                for (int i = 0; i < 20; i++) {
135                        float r = minDist + random.nextFloat() * (maxDist - minDist); 
136                        loc = Misc.getPointAtRadius(pf.getLocation(), r, random);
137                        if (!Misc.isInsideSlipstream(loc, 500f, pf.getContainingLocation())) {
138                                break;
139                        }
140                }
141                if (Misc.isInsideSlipstream(loc, 500f, pf.getContainingLocation())) {
142                        return false;
143                }
144                
145                getMovement().getLocation().set(loc);
146                entity.getLocation().set(movement.getLocation());
147                entity.getVelocity().set(movement.getVelocity());
148                
149                return true;
150        }
151        
152        public void placeNearEntity(SectorEntityToken entity, float minDist, float maxDist) {
153                Random random = getRandom();
154                Vector2f loc = new Vector2f();
155                
156                float r = minDist + random.nextFloat() * (maxDist - minDist); 
157                loc = Misc.getPointAtRadius(entity.getLocation(), r, random);
158                
159                getMovement().getLocation().set(loc);
160                entity.getLocation().set(movement.getLocation());
161                entity.getVelocity().set(movement.getVelocity());
162        }
163        
164        public void setLoc(Vector2f loc) {
165                getMovement().getLocation().set(loc);
166                entity.getLocation().set(movement.getLocation());
167        }
168        public void setVel(Vector2f vel) {
169                getMovement().getVelocity().set(vel);
170                entity.getVelocity().set(movement.getVelocity());
171        }
172        
173        public void initEntity(float sensorProfile, float radius) {
174                float maxFleetRadius = Global.getSettings().getFloat("maxFleetSelectionRadius");
175                int extraInds = 0; 
176                if (radius > maxFleetRadius) {
177                        extraInds = (int) Math.round((radius - maxFleetRadius) / 20f);
178                }
179                initEntity(sensorProfile, radius, extraInds);
180        }
181        public void initEntity(float sensorProfile, float radius, int extraSensorInds) {
182                initEntity(sensorProfile, radius, extraSensorInds, Global.getSector().getHyperspace());
183        }
184        public void initEntity(float sensorProfile, float radius, int extraSensorInds, LocationAPI where) {
185                entity = where.addCustomEntity(null, null,
186                                Entities.SENSOR_GHOST, Factions.NEUTRAL);
187                entity.setDiscoverable(true);
188                entity.setSensorProfile(sensorProfile);
189                entity.setDiscoveryXP(0f);
190                entity.setDetectionRangeDetailsOverrideMult(-100f);
191                entity.setRadius(radius);
192                entity.forceSensorFaderOut();
193                
194                despawnRange = Math.max(100f, sensorProfile * 0.25f);
195                //if (despawnRange > 200f) despawnRange = 200f;
196                despawnRange = 100f;
197                
198                if (extraSensorInds > 0) {
199                        entity.getMemoryWithoutUpdate().set(MemFlags.EXTRA_SENSOR_INDICATORS, extraSensorInds);
200                }
201        }
202        
203        public void setNumSensorIndicators(int min, int max, Random random) {
204                if (random == null) random = Misc.random;
205                int num = min + random.nextInt(max - min + 1);
206                entity.getMemoryWithoutUpdate().set(MemFlags.SENSOR_INDICATORS_OVERRIDE, num);
207        }
208        
209        protected void reportDespawning(DespawnReason reason, Object param) {
210                
211        }
212        
213        public void advance(float amount) {
214                if (entity == null) {
215                        return;
216                }
217                if (!entity.hasTag(Tags.FADING_OUT_AND_EXPIRING)) {
218                        if (script.isEmpty() ||
219                                        (despawnOutsideSector && Misc.isOutsideSector(entity.getLocation())) ||
220                                        (despawnInAbyss && Misc.isInAbyss(entity))
221                                        ) {
222                                Misc.fadeAndExpire(entity, 1f);
223                                reportDespawning(DespawnReason.SCRIPT_ENDED, null);
224                                entity = null;
225                                return;
226                        } else {
227                                for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) {
228                                        float dist = Misc.getDistance(entity, fleet);
229                                        dist -= entity.getRadius() + fleet.getRadius();
230                                        if (dist < despawnRange) {
231                                                Misc.fadeAndExpire(entity, 1f);
232                                                reportDespawning(DespawnReason.FLEET_IN_RANGE, fleet);
233                                                entity = null;
234                                                return;
235                                        }
236                                }
237                        }
238                }
239                
240                if (fleeBurnLevel > 0 && !fleeing &&
241                                Global.getSector().getMemoryWithoutUpdate().getBoolean(MemFlags.GLOBAL_INTERDICTION_PULSE_JUST_USED_IN_CURRENT_LOCATION)) {
242                        if (entity.getContainingLocation() != null) {
243                                for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) {
244                                        float range = InterdictionPulseAbility.getRange(fleet);
245                                        float dist = Misc.getDistance(fleet.getLocation(), entity.getLocation());
246                                        if (dist > range) continue;
247                                        if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.JUST_DID_INTERDICTION_PULSE)) {
248                                                fleeing = true;
249                                                script.clear();
250                                                entity.addScript(new SpeedReduction(entity, 0.75f));
251                                                addBehavior(new GBStayInPlace(0.1f + 0.2f * Misc.random.nextFloat()));
252                                                addBehavior(new GBGoAwayFrom(3f + Misc.random.nextFloat() * 2f, fleet, fleeBurnLevel));
253                                                break;
254                                        }
255                                }
256                        }
257                }
258                
259                if (!script.isEmpty()) {
260                        GhostBehavior curr = script.get(0);
261                        curr.advance(amount, this);
262                        
263                        if (curr.isDone()) {
264                                script.remove(curr);
265                        }
266                }
267
268                
269                movement.advance(amount);
270//              if (this instanceof LeviathanGhost) {
271//                      Vector2f prev = entity.getLocation();
272//                      Vector2f next = movement.getLocation();
273//                      if (Misc.getDistance(prev, next) > 100f) {
274//                              System.out.println("LOCATION JUMP");
275//                              movement.advance(amount);
276//                      }
277//              }
278                
279                
280                entity.getLocation().set(movement.getLocation());
281                entity.getVelocity().set(movement.getVelocity());
282                //entity.getVelocity().set(0f, 0f);
283        }
284        
285        
286        public float getAccelMult() {
287                return accelMult;
288        }
289
290        public void setAccelMult(float accelMult) {
291                this.accelMult = accelMult;
292        }
293
294        public void moveTo(Vector2f dest, float maxBurn) {
295                moveTo(dest, null, maxBurn);
296        }
297        
298        public void moveTo(Vector2f dest, Vector2f destVel, float maxBurn) {
299                float speed = Misc.getSpeedForBurnLevel(maxBurn);
300                float accelMult = speed / Misc.getSpeedForBurnLevel(20f);;
301                if (accelMult < 0.5f) accelMult = 0.5f;
302                if (accelMult > 10f) accelMult = 10f;
303                movement.setAcceleration(speed * accelMult * this.accelMult);
304                movement.setMaxSpeed(speed);
305                movement.setDest(dest, destVel);
306        }
307        
308        public int getMaxBurn() {
309                return (int) Misc.getBurnLevelForSpeed(movement.getMaxSpeed());
310        }
311        public int getCurrBurn() {
312                return (int) Misc.getBurnLevelForSpeed(entity.getVelocity().length());
313        }
314        
315        public float getAcceleration() {
316                return movement.getAcceleration();
317        }
318        
319        public SmoothMovementUtil getMovement() {
320                return movement;
321        }
322        
323        public CustomCampaignEntityAPI getEntity() {
324                return entity;
325        }
326
327        public boolean isDone() {
328                return entity == null;
329        }
330
331        public boolean runWhilePaused() {
332                return false;
333        }
334
335        public boolean isDespawnOutsideSector() {
336                return despawnOutsideSector;
337        }
338
339        public void setDespawnOutsideSector(boolean despawnOutsideSector) {
340                this.despawnOutsideSector = despawnOutsideSector;
341        }
342        
343        public boolean isDespawnInAbyss() {
344                return despawnInAbyss;
345        }
346
347        public void setDespawnInAbyss(boolean despawnInAbyss) {
348                this.despawnInAbyss = despawnInAbyss;
349        }
350
351        public boolean isCreationFailed() {
352                return creationFailed;
353        }
354
355        public void setCreationFailed() {
356                this.creationFailed = true;
357                if (entity != null && entity.getContainingLocation() != null) {
358                        entity.getContainingLocation().removeEntity(entity);
359                }
360        }
361
362        public List<GhostBehavior> getScript() {
363                return script;
364        }
365        
366        public void clearScript() {
367                script.clear();
368        }
369}
370
371
372