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