Starsector API
Loading...
Searching...
No Matches
SlipstreamTerrainPlugin.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.terrain;
2
3import java.awt.Color;
4import java.util.ArrayList;
5import java.util.EnumSet;
6import java.util.HashMap;
7import java.util.Iterator;
8import java.util.LinkedHashMap;
9import java.util.List;
10import java.util.Map;
11
12import org.lwjgl.util.vector.Vector2f;
13
14import com.fs.starfarer.api.Global;
15import com.fs.starfarer.api.campaign.CampaignEngineLayers;
16import com.fs.starfarer.api.campaign.CampaignFleetAPI;
17import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
18import com.fs.starfarer.api.campaign.LocationAPI;
19import com.fs.starfarer.api.campaign.SectorEntityToken;
20import com.fs.starfarer.api.combat.ViewportAPI;
21import com.fs.starfarer.api.ui.TooltipMakerAPI;
22import com.fs.starfarer.api.util.Misc;
23import com.fs.starfarer.api.util.TimeoutTracker;
24
26
27 public static final String LOCATION_SLIPSTREAM_KEY = "local_slipstream_terrain";
28
29 public static final float MIN_POINT_DIST = 20f;
30 public static final float MAX_POINT_DIST = 250f;
31
32 public static final float WIDTH_GROWTH_PER_DAY = 200f;
33
34
35 public static CampaignTerrainAPI getSlipstream(LocationAPI location) {
36 if (location == null) return null;
37 return (CampaignTerrainAPI) location.getPersistentData().get(LOCATION_SLIPSTREAM_KEY);
38 }
39
40 public static SlipstreamTerrainPlugin getSlipstreamPlugin(LocationAPI location) {
41 CampaignTerrainAPI slipstream = getSlipstream(location);
42 if (slipstream != null) {
43 return (SlipstreamTerrainPlugin)slipstream.getPlugin();
44 }
45 return null;
46 }
47
48 public static class StreamPoint {
49 public float x, y;
50 public float daysLeft;
51 public float burn;
52 public float width;
53 public StreamPoint(float x, float y, float daysLeft, float burn, float width) {
54 this.x = x;
55 this.y = y;
56 this.daysLeft = daysLeft;
57 this.burn = burn;
58 this.width = width;
59 }
60 }
61
62 public static class Stream {
63 transient public float minX, minY, maxX, maxY;
64 public List<StreamPoint> points = new ArrayList<StreamPoint>();
65 //public List<StreamParticle> particles = new ArrayList<StreamParticle>();
66 public SectorEntityToken key;
67 public int particlesPerPoint;
68
69 public Stream(SectorEntityToken key, int particlesPerPoint) {
70 this.key = key;
71 this.particlesPerPoint = particlesPerPoint;
72 readResolve();
73 }
74
75 Object readResolve() {
76 minX = Float.MAX_VALUE;
77 minY = Float.MAX_VALUE;
78 maxX = -Float.MAX_VALUE;
79 maxY = -Float.MAX_VALUE;
80 for (StreamPoint p : points) {
81 updateMinMax(p);
82 }
83 return this;
84 }
85
86 public boolean isEmpty() {
87 return points.isEmpty();
88 }
89
90 public StreamPoint getLastPoint() {
91 if (isEmpty()) return null;
92 return points.get(points.size() - 1);
93 }
94
95 public StreamPoint getFirstPoint() {
96 if (isEmpty()) return null;
97 return points.get(0);
98 }
99
100 private void updateMinMax(StreamPoint p) {
101 if (p.x < minX) minX = p.x;
102 if (p.y < minY) minY = p.y;
103 if (p.x > maxX) maxX = p.x;
104 if (p.y > maxY) maxY = p.y;
105 }
106
107 public void addPoint(StreamPoint p) {
108 updateMinMax(p);
109 points.add(p);
110 }
111
112 public boolean isNearViewport(ViewportAPI v, float pad) {
113 float x = v.getLLX();
114 float y = v.getLLY();
115 float w = v.getVisibleWidth();
116 float h = v.getVisibleHeight();
117
118 if (minX > x + w + pad) return false;
119 if (minY > y + h + pad) return false;
120 if (maxX < x - pad) return false;
121 if (maxY < y - pad) return false;
122
123 return true;
124 }
125
126 public boolean couldContainLocation(Vector2f loc, float radius) {
127 if (minX > loc.x + radius) return false;
128 if (minY > loc.y + radius) return false;
129 if (maxX < loc.x - radius) return false;
130 if (maxY < loc.y - radius) return false;
131 return true;
132 }
133 }
134
135
136 protected Map<SectorEntityToken, Stream> streams = new LinkedHashMap<SectorEntityToken, Stream>();
137 transient protected Map<SectorEntityToken, StreamPoint> containsCache = new HashMap<SectorEntityToken, StreamPoint>();
138 //transient protected SpriteAPI pointTexture;
139
140 protected TimeoutTracker<SectorEntityToken> disrupted = new TimeoutTracker<SectorEntityToken>();
141
142 public void init(String terrainId, SectorEntityToken entity, Object param) {
143 super.init(terrainId, entity, param);
144 readResolve();
145 }
146
147 public void advance(float amount) {
148 super.advance(amount);
149 if (true) return;
150
151 containsCache.clear();
152
153 float days = Global.getSector().getClock().convertToDays(amount);
154
155 disrupted.advance(days);
156
157 for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) {
158 //if (!fleet.isPlayerFleet()) continue;
159 if (disrupted.contains(fleet)) continue;
160
161 float burnLevel = fleet.getCurrBurnLevel();
162 if (burnLevel >= 1) {
163 Stream s = getStream(fleet);
164 Vector2f loc = fleet.getLocation();
165 boolean addPoint = false;
166 if (s.isEmpty()) {
167 addPoint = true;
168 } else {
169 StreamPoint last = s.getLastPoint();
170 float dist = Misc.getDistance(loc.x, loc.y, last.x, last.y);
171 if (dist > MIN_POINT_DIST) {
172 addPoint = true;
173 }
174 }
175 if (addPoint) {
176 StreamPoint p = new StreamPoint(loc.x, loc.y, 0.25f,
177 burnLevel,
178 Math.max(MIN_POINT_DIST * 2f, fleet.getRadius()));
179 s.addPoint(p);
180 }
181 }
182 }
183
184 Iterator<Stream> iter1 = streams.values().iterator();
185 while (iter1.hasNext()) {
186 Stream s = iter1.next();
187
188 advancePoints(s, days);
189 //advanceParticles(s, days);
190 //addNewParticles(s, days);
191
192 if (s.isEmpty()) {// && s.particles.isEmpty()) {
193 iter1.remove();
194 }
195 }
196 }
197
198 public TimeoutTracker<SectorEntityToken> getDisrupted() {
199 return disrupted;
200 }
201
202 public void disrupt(CampaignFleetAPI fleet, float dur) {
203 disrupted.set(fleet, dur);
204 }
205
206 private void advancePoints(Stream s, float days) {
207 Iterator<StreamPoint> iter2 = s.points.iterator();
208 while (iter2.hasNext()) {
209 StreamPoint p = iter2.next();
210 p.daysLeft -= days;
211 p.width += WIDTH_GROWTH_PER_DAY * days;
212 if (p.daysLeft <= 0) {
213 iter2.remove();
214 }
215 }
216 }
217
218 protected Stream getStream(SectorEntityToken key) {
219 Stream s = streams.get(key);
220 if (s == null) {
221 s = new Stream(key, 50);
222 streams.put(key, s);
223 }
224 return s;
225 }
226
227 @Override
228 public void applyEffect(SectorEntityToken entity, float days) {
229 if (entity instanceof CampaignFleetAPI) {
230 CampaignFleetAPI fleet = (CampaignFleetAPI) entity;
231 containsPointCaching(fleet, fleet.getLocation(), fleet.getRadius());
232 if (containsCache.containsKey(fleet)) {
233 StreamPoint point = containsCache.get(fleet);
234 float fleetBurn = fleet.getFleetData().getBurnLevel();
235 if (point.burn > fleetBurn || true) {
236 float diff = point.burn - fleetBurn;
237 if (diff > 2) diff = 2;
238 diff = (int) diff;
239 diff = 2;
240 fleet.getStats().addTemporaryModFlat(0.1f, getModId(), "In slipstream", diff, fleet.getStats().getFleetwideMaxBurnMod());
241 }
242 }
243 }
244 }
245
246 @Override
247 public boolean containsEntity(SectorEntityToken other) {
248 return containsPointCaching(other, other.getLocation(), other.getRadius()) && !isPreventedFromAffecting(other);
249 }
250
251 @Override
252 public boolean containsPoint(Vector2f point, float radius) {
253 return containsPointCaching(null, point, radius);
254 }
255
256 private boolean containsPointCaching(SectorEntityToken key, Vector2f point, float radius) {
257 if (key != null && containsCache.containsKey(key)) {
258 return true;
259 }
260 if (true) return false;
261
262 float maxBurn = 0f;
263 StreamPoint result = null;
264 for (Stream s : streams.values()) {
265 if (s.key == key) continue;
266
267 if (!s.couldContainLocation(point, radius)) continue;
268 for (StreamPoint p : s.points) {
269// if (key instanceof CampaignFleetAPI) {
270// float fleetBurn = ((CampaignFleetAPI)key).getFleetData().getBurnLevel();
271// if (p.burn <= fleetBurn) {
272// continue;
273// }
274// }
275
276 float dist = Misc.getDistance(p.x, p.y, point.x, point.y);
277 if (dist < p.width && maxBurn < p.burn) {
278 maxBurn = p.burn;
279 result = p;
280 }
281 }
282 }
283
284 if (result != null && key != null) {
285 containsCache.put(key, result);
286 }
287
288 return result != null;
289 }
290
291
292
293 Object readResolve() {
294 layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7);
295 //pointTexture = Global.getSettings().getSprite("terrain", "slipstream_point");
296 if (containsCache == null) {
297 containsCache = new HashMap<SectorEntityToken, StreamPoint>();
298 }
299 return this;
300 }
301
302 Object writeReplace() {
303 return this;
304 }
305
306 transient private EnumSet<CampaignEngineLayers> layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7);
307 public EnumSet<CampaignEngineLayers> getActiveLayers() {
308 return layers;
309 }
310
311 @Override
312 public boolean stacksWithSelf() {
313 return super.stacksWithSelf();
314 }
315
316 @Override
317 public float getRenderRange() {
318 return Float.MAX_VALUE;
319 }
320
321 public boolean hasTooltip() {
322 return true;
323 }
324
325 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
326 float pad = 10f;
327 Color gray = Misc.getGrayColor();
328 Color highlight = Misc.getHighlightColor();
329 Color fuel = Global.getSettings().getColor("progressBarFuelColor");
330 Color bad = Misc.getNegativeHighlightColor();
331
332 tooltip.addTitle(getTerrainName());
333 tooltip.addPara("Reduces the range at which fleets inside it can be detected by %s.", pad,
334 highlight,
335 "50%"
336 );
337 tooltip.addPara("Does not stack with other similar terrain effects.", pad);
338 }
339
340 public boolean isTooltipExpandable() {
341 return false;
342 }
343
344 public float getTooltipWidth() {
345 return 350f;
346 }
347
348 public String getTerrainName() {
349 return "Slipstream";
350 }
351
352 public String getNameForTooltip() {
353 return "Slipstream";
354 }
355
356 public String getEffectCategory() {
357 return "slipstream";
358 }
359}
360
361
362
363//public static class StreamParticle {
364// public float x, y;
365// public float age, maxAge;
366// public StreamPoint point;
367// //public Vector2f lastVel = null;
368// //public LinkedList<Vector2f> prevLocs = new LinkedList<Vector2f>();
369// public Color color;
370//}
371
372//private void advanceParticles(Stream s, float days) {
373// Iterator<StreamParticle> iter3 = s.particles.iterator();
374// while (iter3.hasNext()) {
375// StreamParticle p = iter3.next();
376// p.age += days;
377// if (p.age >= p.maxAge) {
378// iter3.remove();
379// } else if (p.point != null) {
380// StreamPoint curr = p.point;
381// StreamPoint prev = null, next = null;
382// int index = s.points.indexOf(curr);
383// if (index != -1) {
384// if (index > 0) {
385// prev = s.points.get(index - 1);
386// }
387// if (index < s.points.size() - 1) {
388// next = s.points.get(index + 1);
389// }
390// }
391// Vector2f vel = new Vector2f();
392// Vector2f cv = new Vector2f(curr.x, curr.y);
393// Vector2f pLoc = new Vector2f(p.x, p.y);
394// float maxSpeed = 1000f;
395// float minSpeed = 1000f;
396// float maxDist = 200f;
397// Vector2f unitDirPrev = null;
398// if (prev != null) {
399// Vector2f pv = new Vector2f(prev.x, prev.y);
400// Vector2f dir = Vector2f.sub(cv, pv, new Vector2f());
401// unitDirPrev = new Vector2f(dir);
402// Misc.normalise(dir);
403// float dist = Misc.getDistance(pLoc, pv);
404// float speed = minSpeed;
405// if (dist < maxDist) {
406// speed = minSpeed + (maxSpeed - minSpeed) * (1f - dist / maxDist);
407// }
408// dir.scale(speed);
409// Vector2f.add(vel, dir, vel);
410// }
411// if (next != null) {
412// Vector2f nv = new Vector2f(next.x, next.y);
413// Vector2f dir = Vector2f.sub(nv, cv, new Vector2f());
414// Misc.normalise(dir);
415// float dist = Misc.getDistance(cv, pLoc);
416// boolean angleOk = unitDirPrev == null || Vector2f.dot(unitDirPrev, dir) > 0;
417// if (dist < MAX_POINT_DIST && angleOk) {
418// float speed = minSpeed;
419// if (dist < maxDist) {
420// speed = minSpeed + (maxSpeed - minSpeed) * (1f - dist / maxDist);
421// }
422// dir.scale(speed);
423// Vector2f.add(vel, dir, vel);
424//
425// float distNext = Misc.getDistance(nv, pLoc);
426// if (distNext < dist) {
427// p.point = next;
428// }
429// }
430// }
431//
432// p.x += vel.x * days;
433// p.y += vel.y * days;
434//
435// pLoc.set(p.x, p.y);
436// if (p.prevLocs.size() < 2) {
437// p.prevLocs.add(pLoc);
438// } else {
439// float dist = Misc.getDistance(p.prevLocs.get(p.prevLocs.size() - 2), p.prevLocs.getLast());
440// if (dist >= 2) {
441// p.prevLocs.add(pLoc);
442// } else {
443// p.prevLocs.getLast().set(pLoc);
444// }
445// }
446//
447//
448// while (p.prevLocs.size() > 5) {
449// p.prevLocs.removeFirst();
450// }
451// }
452// }
453//}
454//
455//
456//private void addNewParticles(Stream s, float days) {
457// if (s.isEmpty()) return;
458//
459// if (!s.key.isInCurrentLocation() || !s.isNearViewport(Global.getSector().getViewport(), 500f)) {
460// return;
461// }
462//
463// int points = s.points.size();
464// int maxParticles = (points - 0) * s.particlesPerPoint;
465//
466// WeightedRandomPicker<Color> colorPicker = new WeightedRandomPicker<Color>();
467// if (s.key instanceof CampaignFleetAPI) {
468// CampaignFleetAPI fleet = (CampaignFleetAPI) s.key;
469// for (FleetMemberViewAPI view : fleet.getViews()) {
470// colorPicker.add(view.getContrailColor().getCurr(), view.getMember().getHullSpec().getHullSize().ordinal());
471// }
472// }
473// if (colorPicker.isEmpty()) {
474// colorPicker.add(new Color(100, 150, 255, 255), 1f);
475// }
476//
477// Random r = new Random();
478// for (int i = 0; i < s.particlesPerPoint * 0.1f; i++) {
479// if (s.particles.size() >= maxParticles || s.isEmpty() || s.points.size() <= 1) {
480// break;
481// }
482//
483// int index = r.nextInt(s.points.size() - 1);
484// StreamPoint p = s.points.get(index);
485// if (index < s.points.size() - 1) {
486// StreamPoint next = s.points.get(index + 1);
487// float dist = Misc.getDistance(p.x, p.y, next.x, next.y);
488// if (dist > MAX_POINT_DIST) continue;
489// }
490//
491// StreamParticle particle = new StreamParticle();
492// float spread = 10f + (s.points.size() - index) * 10f;
493// particle.x = p.x + (float) Math.random() * spread - spread/2f;
494// particle.y = p.y + (float) Math.random() * spread - spread/2f;
497// particle.age = 0;
498// particle.maxAge = 0.1f + 0.1f * (float) Math.random();
499// particle.point = p;
500//
501// particle.color = colorPicker.pick();
502//
503// s.particles.add(particle);
504// }
505//}
506//public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
507// super.render(layer, viewport);
508//
509// float alphaMult = viewport.getAlphaMult();
510//
511// for (Stream s : streams.values()) {
512// if (!s.isNearViewport(viewport, 300f)) continue;
513// if (s.particles.isEmpty()) continue;
514// GL11.glEnable(GL11.GL_BLEND);
515// GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
516// //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
517// //Color color = new Color(100, 150, 255, 255);
522//
523// pointTexture.bindTexture();
524// GL11.glBegin(GL11.GL_QUADS);
525// StreamPoint last = null;
526// if (!s.points.isEmpty()) {
527// last = s.points.get(s.points.size() - 1);
528// }
529// for (StreamParticle p : s.particles) {
565//
566// float b;
567// float t = Math.min(0.05f, p.maxAge / 2f);
568// if (p.age < t) {
569// b = p.age / t;
570// } else {
571// b = 1f - (p.age - t) / (p.maxAge - t);
572// }
573// b *= 4f;
574//
575// if (last != null) {
579// float dist = Misc.getDistance(last.x, last.y, p.x, p.y);
580// float max = s.key.getRadius() + 50f;
581// if (dist < max) {
582// b *= Math.max(0, (dist - s.key.getRadius())/ (max - s.key.getRadius()));
583// if (b <= 0) {
584// p.age = p.maxAge + 1f;
585// }
586// }
587// }
588// if (p.age >= p.maxAge) {
589// b = 0f;
590// }
591//
592// Color color = p.color;
593// float alpha = ((float)color.getAlpha() * alphaMult * b);
594// if (alpha > 255f) alpha = 255f;
595// GL11.glColor4ub((byte)color.getRed(),
596// (byte)color.getGreen(),
597// (byte)color.getBlue(),
598// (byte)(alpha));
599//
600// float size = 7f;
601// GL11.glTexCoord2f(0, 0);
602// GL11.glVertex2f(p.x - size/2f, p.y - size/2f);
603//
604// GL11.glTexCoord2f(0.01f, 0.99f);
605// GL11.glVertex2f(p.x - size/2f, p.y + size/2f);
606//
607// GL11.glTexCoord2f(0.99f, 0.99f);
608// GL11.glVertex2f(p.x + size/2f, p.y + size/2f);
609//
610// GL11.glTexCoord2f(0.99f, 0.01f);
611// GL11.glVertex2f(p.x + size/2f, p.y - size/2f);
612// }
613//
614// GL11.glEnd();
615//
616// }
617//}
static SettingsAPI getSettings()
Definition Global.java:51
static SectorAPI getSector()
Definition Global.java:59
boolean isPreventedFromAffecting(SectorEntityToken other)
static SlipstreamTerrainPlugin getSlipstreamPlugin(LocationAPI location)
void init(String terrainId, SectorEntityToken entity, Object param)