Starsector API
Loading...
Searching...
No Matches
MoteControlScript.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.combat;
2
3import java.awt.Color;
4import java.util.ArrayList;
5import java.util.HashMap;
6import java.util.Iterator;
7import java.util.List;
8import java.util.Map;
9
10import org.lwjgl.util.vector.Vector2f;
11
12import com.fs.starfarer.api.Global;
13import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
14import com.fs.starfarer.api.combat.CollisionClass;
15import com.fs.starfarer.api.combat.CombatEngineAPI;
16import com.fs.starfarer.api.combat.CombatEngineLayers;
17import com.fs.starfarer.api.combat.CombatEntityAPI;
18import com.fs.starfarer.api.combat.DamageType;
19import com.fs.starfarer.api.combat.EmpArcEntityAPI;
20import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
21import com.fs.starfarer.api.combat.MissileAPI;
22import com.fs.starfarer.api.combat.MutableShipStatsAPI;
23import com.fs.starfarer.api.combat.ShipAPI;
24import com.fs.starfarer.api.combat.ShipSystemAPI;
25import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState;
26import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
27import com.fs.starfarer.api.impl.campaign.ids.HullMods;
28import com.fs.starfarer.api.input.InputEventAPI;
29import com.fs.starfarer.api.loading.WeaponSlotAPI;
30import com.fs.starfarer.api.util.IntervalUtil;
31import com.fs.starfarer.api.util.Misc;
32import com.fs.starfarer.api.util.WeightedRandomPicker;
33
35
36 protected static float MAX_ATTRACTOR_RANGE = 3000f;
37 public static float MAX_DIST_FROM_SOURCE_TO_ENGAGE_AS_PD = 2000f;
38 public static float MAX_DIST_FROM_ATTRACTOR_TO_ENGAGE_AS_PD = 1000f;
39
40 public static int MAX_MOTES = 30;
41 public static int MAX_MOTES_HF = 50;
42
43 public static float ANTI_FIGHTER_DAMAGE = 200;
44 public static float ANTI_FIGHTER_DAMAGE_HF = 1000;
45
46 public static float ATTRACTOR_DURATION_LOCK = 20f;
47 public static float ATTRACTOR_DURATION = 10f;
48
49 //public static Color JITTER_COLOR = new Color(100,155,255,175);
50
51
52// static {
53// // .wpn
54// Color muzzleFlashColor = new Color(100,165,255,25);
55//
56// // .proj
57// Color hitGlowColor = new Color(100,100,255,255);
58// Color engineGlowColor = new Color(100,165,255,255);
59// Color contrailColor = new Color(100,165,255,25);
60//
61// //MoteControlScript
62// Color jitterColor = new Color(100,165,255,175);
63// Color empArcColor = new Color(100,165,255,255);
64//
65// // on hit effect
66// Color onHitEmpColor = new Color(100,165,255,255);
67// }
68
69 public static class MoteData {
70 public Color jitterColor;
71 public Color empColor;
72
73 public int maxMotes;
74
75 public float antiFighterDamage;
76 public String impactSound;
77 public String loopSound;
78 }
79
80 public static Map<String, MoteData> MOTE_DATA = new HashMap<String, MoteData>();
81
82 public static String MOTELAUNCHER = "motelauncher";
83 public static String MOTELAUNCHER_HF = "motelauncher_hf";
84
85 static {
86 MoteData normal = new MoteData();
87 normal.jitterColor = new Color(100,165,255,175);
88 normal.empColor = new Color(100,165,255,255);
89 normal.maxMotes = MAX_MOTES;
90 normal.antiFighterDamage = ANTI_FIGHTER_DAMAGE;
91 normal.impactSound = "mote_attractor_impact_normal";
92 normal.loopSound = "mote_attractor_loop";
93
94 MOTE_DATA.put(MOTELAUNCHER, normal);
95
96 MoteData hf = new MoteData();
97 hf.jitterColor = new Color(255,100,255,175);
98 hf.empColor = new Color(255,100,255,255);
99 hf.maxMotes = MAX_MOTES_HF;
100 hf.antiFighterDamage = ANTI_FIGHTER_DAMAGE_HF;
101 hf.impactSound = "mote_attractor_impact_damage";
102 hf.loopSound = "mote_attractor_loop_dark";
103
104 MOTE_DATA.put(MOTELAUNCHER_HF, hf);
105 }
106
107 public static boolean isHighFrequency(ShipAPI ship) {
108 //if (true) return true;
109 return ship != null && ship.getVariant().hasHullMod(HullMods.HIGH_FREQUENCY_ATTRACTOR);
110 }
111
112 public static String getWeaponId(ShipAPI ship) {
113 if (isHighFrequency(ship)) return MOTELAUNCHER_HF;
114 return MOTELAUNCHER;
115 }
116
117 public static float getAntiFighterDamage(ShipAPI ship) {
118 return MOTE_DATA.get(getWeaponId(ship)).antiFighterDamage;
119 }
120 public static String getImpactSoundId(ShipAPI ship) {
121 return MOTE_DATA.get(getWeaponId(ship)).impactSound;
122 }
123 public static Color getJitterColor(ShipAPI ship) {
124 return MOTE_DATA.get(getWeaponId(ship)).jitterColor;
125 }
126 public static Color getEMPColor(ShipAPI ship) {
127 return MOTE_DATA.get(getWeaponId(ship)).empColor;
128 }
129
130 public static int getMaxMotes(ShipAPI ship) {
131 return MOTE_DATA.get(getWeaponId(ship)).maxMotes;
132 }
133
134 public static String getLoopSound(ShipAPI ship) {
135 return MOTE_DATA.get(getWeaponId(ship)).loopSound;
136 }
137
138
139 public static class SharedMoteAIData {
140 public float elapsed = 0f;
141 public List<MissileAPI> motes = new ArrayList<MissileAPI>();
142
143 public float attractorRemaining = 0f;
144 public Vector2f attractorTarget = null;
145 public ShipAPI attractorLock = null;
146 }
147
148 public static SharedMoteAIData getSharedData(ShipAPI source) {
149 String key = source + "_mote_AI_shared";
150 SharedMoteAIData data = (SharedMoteAIData)Global.getCombatEngine().getCustomData().get(key);
151 if (data == null) {
152 data = new SharedMoteAIData();
153 Global.getCombatEngine().getCustomData().put(key, data);
154 }
155 return data;
156 }
157
158
159
160 protected IntervalUtil launchInterval = new IntervalUtil(0.75f, 1.25f);
161 protected IntervalUtil attractorParticleInterval = new IntervalUtil(0.05f, 0.1f);
162 protected WeightedRandomPicker<WeaponSlotAPI> launchSlots = new WeightedRandomPicker<WeaponSlotAPI>();
163 protected WeaponSlotAPI attractor = null;
164
165 //protected int empCount = 0;
166 protected boolean findNewTargetOnUse = true;
167
168 protected void findSlots(ShipAPI ship) {
169 if (attractor != null) return;
170 for (WeaponSlotAPI slot : ship.getHullSpec().getAllWeaponSlotsCopy()) {
171 if (slot.isSystemSlot()) {
172 if (slot.getSlotSize() == WeaponSize.SMALL) {
173 launchSlots.add(slot);
174 }
175 if (slot.getSlotSize() == WeaponSize.MEDIUM) {
176 attractor = slot;
177 }
178 }
179 }
180 }
181
182 public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
183 ShipAPI ship = null;
184 if (stats.getEntity() instanceof ShipAPI) {
185 ship = (ShipAPI) stats.getEntity();
186 } else {
187 return;
188 }
189
190 float amount = Global.getCombatEngine().getElapsedInLastFrame();
191
192 //Global.getCombatEngine().setPaused(true);
193
194 SharedMoteAIData data = getSharedData(ship);
195 data.elapsed += amount;
196
197 if (data.attractorRemaining > 0) {
198 data.attractorRemaining -= amount;
199 if (data.attractorRemaining <= 0 ||
200 (data.attractorLock != null && !data.attractorLock.isAlive()) ||
201 data.motes.isEmpty()) {
202 data.attractorTarget = null;
203 data.attractorLock = null;
204 data.attractorRemaining = 0;
205 }
206 }
207 if (effectLevel <= 0) {
208 findNewTargetOnUse = true;
209 }
210
211 CombatEngineAPI engine = Global.getCombatEngine();
212
213 attractorParticleInterval.advance(amount);
214 if (attractorParticleInterval.intervalElapsed()) {
216 }
217
218 launchInterval.advance(amount * 5f);
219 if (launchInterval.intervalElapsed()) {
220 Iterator<MissileAPI> iter = data.motes.iterator();
221 while (iter.hasNext()) {
222 if (!engine.isMissileAlive(iter.next())) {
223 iter.remove();
224 }
225 }
226
227 if (ship.isHulk()) {
228 for (MissileAPI mote : data.motes) {
229 mote.flameOut();
230 }
231 data.motes.clear();
232 return;
233 }
234
235 int maxMotes = getMaxMotes(ship);
236 if (data.motes.size() < maxMotes && data.attractorLock == null &&// false &&
237 !ship.getFluxTracker().isOverloadedOrVenting()) {
238 findSlots(ship);
239
240 WeaponSlotAPI slot = launchSlots.pick();
241
242 Vector2f loc = slot.computePosition(ship);
243 float dir = slot.computeMidArcAngle(ship);
244 float arc = slot.getArc();
245 dir += arc * (float) Math.random() - arc /2f;
246
247 String weaponId = getWeaponId(ship);
248 MissileAPI mote = (MissileAPI) engine.spawnProjectile(ship, null,
249 weaponId,
250 loc, dir, null);
251 mote.setWeaponSpec(weaponId);
252 mote.setMissileAI(new MoteAIScript(mote));
253 mote.getActiveLayers().remove(CombatEngineLayers.FF_INDICATORS_LAYER);
254 // if they could flame out/be affected by emp, that'd be bad since they don't expire for a
255 // very long time so they'd be stuck disabled permanently, for practical purposes
256 // thus: total emp resistance (which can't target them anyway, but if it did.)
257 mote.setEmpResistance(10000);
258 data.motes.add(mote);
259
260 engine.spawnMuzzleFlashOrSmoke(ship, slot, mote.getWeaponSpec(), 0, dir);
261
262 Global.getSoundPlayer().playSound("mote_attractor_launch_mote", 1f, 0.25f, loc, new Vector2f());
263 }
264 }
265
266 float maxMotes = getMaxMotes(ship);
267 float fraction = data.motes.size() / (Math.max(1f, maxMotes));
268 float volume = fraction * 3f;
269 if (volume > 1f) volume = 1f;
270 if (data.motes.size() > 3) {
271 Vector2f com = new Vector2f();
272 for (MissileAPI mote : data.motes) {
273 Vector2f.add(com, mote.getLocation(), com);
274 }
275 com.scale(1f / data.motes.size());
276 //Global.getSoundPlayer().playLoop("mote_attractor_loop", ship, 1f, volume, com, new Vector2f());
277 Global.getSoundPlayer().playLoop(getLoopSound(ship), ship, 1f, volume, com, new Vector2f());
278 }
279
280
281 if (effectLevel > 0 && findNewTargetOnUse) {
283 findNewTargetOnUse = false;
284 }
285
286 if (effectLevel == 1) {
287 // possible if system is reused immediately w/ no time to cool down, I think
288 if (data.attractorTarget == null) {
290 }
291 findSlots(ship);
292
293 Vector2f slotLoc = attractor.computePosition(ship);
294
295 CombatEntityAPI asteroid = engine.spawnAsteroid(0, data.attractorTarget.x, data.attractorTarget.y, 0, 0);
296 asteroid.setCollisionClass(CollisionClass.NONE);
297 CombatEntityAPI target = asteroid;
298 if (data.attractorLock != null) {
299 target = data.attractorLock;
300 }
301
302 float emp = 0;
303 float dam = 0;
304 EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArc(ship, slotLoc, ship, target,
305 DamageType.ENERGY,
306 dam,
307 emp, // emp
308 100000f, // max range
309 "mote_attractor_targeted_ship",
310 40f, // thickness
311 //new Color(100,165,255,255),
313 new Color(255,255,255,255)
314 );
315 if (data.attractorLock != null) {
316 arc.setTargetToShipCenter(slotLoc, data.attractorLock);
317 }
318 arc.setCoreWidthOverride(30f);
319
320 if (data.attractorLock == null) {
321 Global.getSoundPlayer().playSound("mote_attractor_targeted_empty_space", 1f, 1f, data.attractorTarget, new Vector2f());
322 }
323
324// Vector2f targetLoc = new Vector2f(target.getLocation());
325// int glows = 100;
326// float maxRadius = 500f;
327// float minRadius = 300f;
328//
329// if (data.attractorLock != null) {
330// maxRadius += data.attractorLock.getCollisionRadius();
331// minRadius += data.attractorLock.getCollisionRadius();
332// }
333//
334// float minDur = 0.5f;
335// float maxDur = 0.75f;
336// float minSize = 10f;
337// float maxSize = 30f;
338// Color color = MoteControlScript.getEMPColor(ship);
339// for (int i = 0; i < glows; i++) {
340// float radius = minRadius + (float) Math.random() * (maxRadius - minRadius);
341// Vector2f loc = Misc.getPointAtRadius(targetLoc, radius);
342// Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(loc, targetLoc));
343// float dist = Misc.getDistance(loc, targetLoc);
344//
345// float dur = minDur + (float) Math.random() * (maxDur - minDur);
346// float speed = dist / dur;
347// dir.scale(speed);
348//
349// float size = minSize + (float) Math.random() * (maxSize - minSize);
350//
351// engine.addHitParticle(loc, dir, size, 1f, 0.3f, dur, color);
352// engine.addHitParticle(loc, dir, size * 0.33f, 1f, 0.3f, dur, Color.white);
353// }
354
355
356 engine.removeEntity(asteroid);
357 }
358 }
359
360 protected void spawnAttractorParticles(ShipAPI ship) {
361 if (true) return; // just not liking this much
362 SharedMoteAIData data = getSharedData(ship);
363
364 if (data.attractorTarget == null) return;
365
366 CombatEngineAPI engine = Global.getCombatEngine();
367
368 Vector2f targetLoc = data.attractorTarget;
369
370 int glows = 2;
371 float maxRadius = 300f;
372 float minRadius = 200f;
373
374 if (data.attractorLock != null) {
375 maxRadius += data.attractorLock.getCollisionRadius();
376 minRadius += data.attractorLock.getCollisionRadius();
377 targetLoc = data.attractorLock.getShieldCenterEvenIfNoShield();
378 }
379
380 float minDur = 0.5f;
381 float maxDur = 0.75f;
382 float minSize = 15f;
383 float maxSize = 30f;
384 Color color = MoteControlScript.getEMPColor(ship);
385 for (int i = 0; i < glows; i++) {
386 float radius = minRadius + (float) Math.random() * (maxRadius - minRadius);
387 Vector2f loc = Misc.getPointAtRadius(targetLoc, radius);
388 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(loc, targetLoc));
389 float dist = Misc.getDistance(loc, targetLoc);
390
391 float dur = minDur + (float) Math.random() * (maxDur - minDur);
392 float speed = dist / dur;
393 dir.scale(speed);
394
395 float size = minSize + (float) Math.random() * (maxSize - minSize);
396
397 engine.addHitParticle(loc, dir, size, 1f, 0.3f, dur, color);
398 engine.addHitParticle(loc, dir, size * 0.5f, 1f, 0.3f, dur, Color.white);
399 }
400 }
401
402
403 public void unapply(MutableShipStatsAPI stats, String id) {
404 }
405
406 public StatusData getStatusData(int index, State state, float effectLevel) {
407 return null;
408 }
409
410 public void calculateTargetData(ShipAPI ship) {
411 SharedMoteAIData data = getSharedData(ship);
412 Vector2f targetLoc = getTargetLoc(ship);
413 //System.out.println(getTargetedLocation(ship));
414 data.attractorLock = getLockTarget(ship, targetLoc);
415
416 data.attractorRemaining = ATTRACTOR_DURATION;
417 if (data.attractorLock != null) {
418 targetLoc = new Vector2f(data.attractorLock.getLocation());
419 data.attractorRemaining = ATTRACTOR_DURATION_LOCK;
420 }
421 data.attractorTarget = targetLoc;
422
423 if (data.attractorLock != null) {
424 // need to do this in a script because when the ship is phased, the charge-in time of the system (0.1s)
425 // is not enough for the jitter to come to full effect (which requires 0.1s "normal" time)
426 Global.getCombatEngine().addPlugin(createTargetJitterPlugin(data.attractorLock,
427 ship.getSystem().getChargeUpDur(), ship.getSystem().getChargeDownDur(),
429 }
430 }
431
432 @Override
433 public String getInfoText(ShipSystemAPI system, ShipAPI ship) {
434 if (system.isOutOfAmmo()) return null;
435 if (system.getState() != SystemState.IDLE) return null;
436
437 boolean inRange = isMouseInRange(ship);
438 //Vector2f targetLoc = getTargetLoc(ship);
439 //ShipAPI target = getLockTarget(ship, targetLoc);
440
441 if (!inRange) {
442 return "OUT OF RANGE";
443 }
444// if (target != null) {
445// return "ENEMY SHIP";
446// }
447// return "AREA";
448 return null;
449 }
450
451
452 @Override
453 public boolean isUsable(ShipSystemAPI system, ShipAPI ship) {
454 return true;
455 }
456
457 public Vector2f getTargetedLocation(ShipAPI from) {
458 Vector2f loc = from.getSystem().getTargetLoc();
459 if (loc == null) {
460 loc = new Vector2f(from.getMouseTarget());
461 }
462 return loc;
463 }
464
465 public Vector2f getTargetLoc(ShipAPI from) {
466 findSlots(from);
467
468 Vector2f slotLoc = attractor.computePosition(from);
469 Vector2f targetLoc = new Vector2f(getTargetedLocation(from));
470 float dist = Misc.getDistance(slotLoc, targetLoc);
471 if (dist > getRange(from)) {
472 targetLoc = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(slotLoc, targetLoc));
473 targetLoc.scale(getRange(from));
474 Vector2f.add(targetLoc, slotLoc, targetLoc);
475 }
476 return targetLoc;
477 }
478
479 public boolean isMouseInRange(ShipAPI from) {
480 Vector2f targetLoc = new Vector2f(from.getMouseTarget());
481 return isLocationInRange(from, targetLoc);
482 }
483
484 public boolean isLocationInRange(ShipAPI from, Vector2f loc) {
485 findSlots(from);
486
487 Vector2f slotLoc = attractor.computePosition(from);
488 float dist = Misc.getDistance(slotLoc, loc);
489 if (dist > getRange(from)) {
490 return false;
491 }
492 return true;
493 }
494
495
496 public ShipAPI getLockTarget(ShipAPI from, Vector2f loc) {
497 Vector2f slotLoc = attractor.computePosition(from);
498 for (ShipAPI other : Global.getCombatEngine().getShips()) {
499 if (other.isFighter()) continue;
500 if (other.getOwner() == from.getOwner()) continue;
501 if (other.isHulk()) continue;
502 if (!other.isTargetable()) continue;
503
504 float dist = Misc.getDistance(slotLoc, other.getLocation());
505 if (dist > getRange(from)) continue;
506
507 dist = Misc.getDistance(loc, other.getLocation());
508 if (dist < other.getCollisionRadius() + 50f) {
509 return other;
510 }
511 }
512 return null;
513 }
514
515 public static float getRange(ShipAPI ship) {
516 if (ship == null) return MAX_ATTRACTOR_RANGE;
517 return ship.getMutableStats().getSystemRangeBonus().computeEffective(MAX_ATTRACTOR_RANGE);
518 }
519
520// public int getMaxMotes() {
521// return MAX_MOTES;
522// }
523
524 protected EveryFrameCombatPlugin createTargetJitterPlugin(final ShipAPI target,
525 final float in,
526 final float out,
527 final Color jitterColor) {
528 return new BaseEveryFrameCombatPlugin() {
529 float elapsed = 0f;
530 @Override
531 public void advance(float amount, List<InputEventAPI> events) {
532 if (Global.getCombatEngine().isPaused()) return;
533
534 elapsed += amount;
535
536
537 float level = 0f;
538 if (elapsed < in) {
539 level = elapsed / in;
540 } else if (elapsed < in + out) {
541 level = 1f - (elapsed - in) / out;
542 level *= level;
543 } else {
544 Global.getCombatEngine().removePlugin(this);
545 return;
546 }
547
548
549 if (level > 0) {
550 float jitterLevel = level;
551 float maxRangeBonus = 50f;
552 float jitterRangeBonus = jitterLevel * maxRangeBonus;
553 target.setJitterUnder(this, jitterColor, jitterLevel, 10, 0f, jitterRangeBonus);
554 target.setJitter(this, jitterColor, jitterLevel, 4, 0f, 0 + jitterRangeBonus);
555 }
556 }
557 };
558 }
559}
560
561
562
563
564
565
566
567
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:43
static CombatEngineAPI getCombatEngine()
Definition Global.java:63
ShipAPI getLockTarget(ShipAPI from, Vector2f loc)
boolean isUsable(ShipSystemAPI system, ShipAPI ship)
String getInfoText(ShipSystemAPI system, ShipAPI ship)
void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel)
void unapply(MutableShipStatsAPI stats, String id)
WeightedRandomPicker< WeaponSlotAPI > launchSlots
StatusData getStatusData(int index, State state, float effectLevel)
boolean isLocationInRange(ShipAPI from, Vector2f loc)
static SharedMoteAIData getSharedData(ShipAPI source)
EveryFrameCombatPlugin createTargetJitterPlugin(final ShipAPI target, final float in, final float out, final Color jitterColor)
void playLoop(String id, Object playingEntity, float pitch, float volume, Vector2f loc, Vector2f vel)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)