Starsector API
Loading...
Searching...
No Matches
MoteAIScript.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.combat;
2
3import java.util.ArrayList;
4import java.util.Iterator;
5import java.util.List;
6
7import org.lwjgl.util.vector.Vector2f;
8
9import com.fs.starfarer.api.Global;
10import com.fs.starfarer.api.combat.CollisionGridAPI;
11import com.fs.starfarer.api.combat.CombatEngineAPI;
12import com.fs.starfarer.api.combat.CombatEntityAPI;
13import com.fs.starfarer.api.combat.MissileAIPlugin;
14import com.fs.starfarer.api.combat.MissileAPI;
15import com.fs.starfarer.api.combat.ShipAPI;
16import com.fs.starfarer.api.combat.ShipCommand;
17import com.fs.starfarer.api.impl.combat.MoteControlScript.SharedMoteAIData;
18import com.fs.starfarer.api.util.FaderUtil;
19import com.fs.starfarer.api.util.IntervalUtil;
20import com.fs.starfarer.api.util.Misc;
21
22public class MoteAIScript implements MissileAIPlugin {
23
24 public static float MAX_FLOCK_RANGE = 500;
25 public static float MAX_HARD_AVOID_RANGE = 200;
26 public static float AVOID_RANGE = 50;
27 public static float COHESION_RANGE = 100;
28
29 public static float ATTRACTOR_LOCK_STOP_FLOCKING_ADD = 300f;
30
31 protected MissileAPI missile;
32
33 protected IntervalUtil tracker = new IntervalUtil(0.05f, 0.1f);
34
35 protected IntervalUtil updateListTracker = new IntervalUtil(0.05f, 0.1f);
36 protected List<MissileAPI> missileList = new ArrayList<MissileAPI>();
37 protected List<CombatEntityAPI> hardAvoidList = new ArrayList<CombatEntityAPI>();
38
39 protected float r;
40
41 protected CombatEntityAPI target;
42 protected SharedMoteAIData data;
43
44 public MoteAIScript(MissileAPI missile) {
45 this.missile = missile;
46 r = (float) Math.random();
47 elapsed = -(float) Math.random() * 0.5f;
48
50
52 }
53
54 public void updateHardAvoidList() {
55 hardAvoidList.clear();
56
57 CollisionGridAPI grid = Global.getCombatEngine().getAiGridShips();
58 Iterator<Object> iter = grid.getCheckIterator(missile.getLocation(), MAX_HARD_AVOID_RANGE * 2f, MAX_HARD_AVOID_RANGE * 2f);
59 while (iter.hasNext()) {
60 Object o = iter.next();
61 if (!(o instanceof ShipAPI)) continue;
62
63 ShipAPI ship = (ShipAPI) o;
64
65 if (ship.isFighter()) continue;
66 hardAvoidList.add(ship);
67 }
68
69 grid = Global.getCombatEngine().getAiGridAsteroids();
70 iter = grid.getCheckIterator(missile.getLocation(), MAX_HARD_AVOID_RANGE * 2f, MAX_HARD_AVOID_RANGE * 2f);
71 while (iter.hasNext()) {
72 Object o = iter.next();
73 if (!(o instanceof CombatEntityAPI)) continue;
74
75 CombatEntityAPI asteroid = (CombatEntityAPI) o;
76 hardAvoidList.add(asteroid);
77 }
78 }
79
80 public void doFlocking() {
81 if (missile.getSource() == null) return;
82
83 ShipAPI source = missile.getSource();
84 CombatEngineAPI engine = Global.getCombatEngine();
85
86 float avoidRange = AVOID_RANGE;
87 float cohesionRange = COHESION_RANGE;
88
89 float sourceRejoin = source.getCollisionRadius() + 200f;
90
91 float sourceRepel = source.getCollisionRadius() + 50f;
92 float sourceCohesion = source.getCollisionRadius() + 600f;
93
94 float sin = (float) Math.sin(data.elapsed * 1f);
95 float mult = 1f + sin * 0.25f;
96 avoidRange *= mult;
97
98 Vector2f total = new Vector2f();
99 Vector2f attractor = getAttractorLoc();
100
101 if (attractor != null) {
102 float dist = Misc.getDistance(missile.getLocation(), attractor);
103 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(missile.getLocation(), attractor));
104 float f = dist / 200f;
105 if (f > 1f) f = 1f;
106 dir.scale(f * 3f);
107 Vector2f.add(total, dir, total);
108
109 avoidRange *= 3f;
110 }
111
112 boolean hardAvoiding = false;
113 for (CombatEntityAPI other : hardAvoidList) {
114 float dist = Misc.getDistance(missile.getLocation(), other.getLocation());
115 float hardAvoidRange = other.getCollisionRadius() + avoidRange + 50f;
116 if (dist < hardAvoidRange) {
117 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(other.getLocation(), missile.getLocation()));
118 float f = 1f - dist / (hardAvoidRange);
119 dir.scale(f * 5f);
120 Vector2f.add(total, dir, total);
121 hardAvoiding = f > 0.5f;
122 }
123 }
124
125
126 //for (MissileAPI otherMissile : missileList) {
127 for (MissileAPI otherMissile : data.motes) {
128 if (otherMissile == missile) continue;
129
130 float dist = Misc.getDistance(missile.getLocation(), otherMissile.getLocation());
131
132
133 float w = otherMissile.getMaxHitpoints();
134 w = 1f;
135
136 float currCohesionRange = cohesionRange;
137
138 if (dist < avoidRange && otherMissile != missile && !hardAvoiding) {
139 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(otherMissile.getLocation(), missile.getLocation()));
140 float f = 1f - dist / avoidRange;
141 dir.scale(f * w);
142 Vector2f.add(total, dir, total);
143 }
144
145 if (dist < currCohesionRange) {
146 Vector2f dir = new Vector2f(otherMissile.getVelocity());
147 Misc.normalise(dir);
148 float f = 1f - dist / currCohesionRange;
149 dir.scale(f * w);
150 Vector2f.add(total, dir, total);
151 }
152
153// if (dist < cohesionRange && dist > avoidRange) {
154// //Vector2f dir = Utils.getUnitVectorAtDegreeAngle(Utils.getAngleInDegrees(missile.getLocation(), mote.getLocation()));
155// Vector2f dir = Utils.getUnitVectorAtDegreeAngle(Utils.getAngleInDegrees(mote.getLocation(), missile.getLocation()));
156// float f = dist / cohesionRange - 1f;
157// dir.scale(f * 0.5f);
158// Vector2f.add(total, dir, total);
159// }
160 }
161
162 if (missile.getSource() != null) {
163 float dist = Misc.getDistance(missile.getLocation(), source.getLocation());
164 if (dist > sourceRejoin) {
165 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(missile.getLocation(), source.getLocation()));
166 float f = dist / (sourceRejoin + 400f) - 1f;
167 dir.scale(f * 0.5f);
168
169 Vector2f.add(total, dir, total);
170 }
171
172 if (dist < sourceRepel) {
173 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(source.getLocation(), missile.getLocation()));
174 float f = 1f - dist / sourceRepel;
175 dir.scale(f * 5f);
176 Vector2f.add(total, dir, total);
177 }
178
179 if (dist < sourceCohesion && source.getVelocity().length() > 20f) {
180 Vector2f dir = new Vector2f(source.getVelocity());
181 Misc.normalise(dir);
182 float f = 1f - dist / sourceCohesion;
183 dir.scale(f * 1f);
184 Vector2f.add(total, dir, total);
185 }
186
187 // if not strongly going anywhere, circle the source ship; only kicks in for lone motes
188 if (total.length() <= 0.05f) {
189 float offset = r > 0.5f ? 90f : -90f;
190 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(
191 Misc.getAngleInDegrees(missile.getLocation(), source.getLocation()) + offset);
192 float f = 1f;
193 dir.scale(f * 1f);
194 Vector2f.add(total, dir, total);
195 }
196 }
197
198 if (total.length() > 0) {
199 float dir = Misc.getAngleInDegrees(total);
200 engine.headInDirectionWithoutTurning(missile, dir, 10000);
201
202 if (r > 0.5f) {
203 missile.giveCommand(ShipCommand.TURN_LEFT);
204 } else {
205 missile.giveCommand(ShipCommand.TURN_RIGHT);
206 }
207 missile.getEngineController().forceShowAccelerating();
208 }
209 }
210
211 //public void accumulate(FlockingData data, Vector2f )
212
213
214 protected IntervalUtil flutterCheck = new IntervalUtil(2f, 4f);
215 protected FaderUtil currFlutter = null;
216 protected float flutterRemaining = 0f;
217
218 protected float elapsed = 0f;
219 public void advance(float amount) {
220 if (missile.isFizzling()) return;
221 if (missile.getSource() == null) return;
222
223 elapsed += amount;
224
225 updateListTracker.advance(amount);
226 if (updateListTracker.intervalElapsed()) {
228 }
229
230 //missile.getEngineController().getShipEngines().get(0).
231
232 if (flutterRemaining <= 0) {
233 flutterCheck.advance(amount);
234 if (flutterCheck.intervalElapsed() &&
235 ((float) Math.random() > 0.9f ||
236 (data.attractorLock != null && (float) Math.random() > 0.5f))) {
237 flutterRemaining = 2f + (float) Math.random() * 2f;
238 }
239 }
240
241// if (flutterRemaining > 0) {
242// flutterRemaining -= amount;
243// if (currFlutter == null) {
244// float min = 1/15f;
245// float max = 1/4f;
246// float dur = min + (max - min) * (float) Math.random();
247// //dur *= 0.5f;
248// currFlutter = new FaderUtil(0f, dur/2f, dur/2f, false, true);
249// currFlutter.fadeIn();
250// }
251// currFlutter.advance(amount);
252// if (currFlutter.isFadedOut()) {
253// currFlutter = null;
254// }
255// } else {
256// currFlutter = null;
257// }
258//
259// if (currFlutter != null) {
260// missile.setGlowRadius(currFlutter.getBrightness() * 30f);
261// } else {
262// missile.setGlowRadius(0f);
263// }
264// if (true) {
265// doFlocking();
266// return;
267// }
268
269
270 if (elapsed >= 0.5f) {
271
272 boolean wantToFlock = !isTargetValid();
273 if (data.attractorLock != null) {
274 float dist = Misc.getDistance(missile.getLocation(), data.attractorLock.getLocation());
275 if (dist > data.attractorLock.getCollisionRadius() + ATTRACTOR_LOCK_STOP_FLOCKING_ADD) {
276 wantToFlock = true;
277 }
278 }
279
280 if (wantToFlock) {
281 doFlocking();
282 } else {
283 CombatEngineAPI engine = Global.getCombatEngine();
284 Vector2f targetLoc = engine.getAimPointWithLeadForAutofire(missile, 1.5f, target, 50);
285 engine.headInDirectionWithoutTurning(missile,
286 Misc.getAngleInDegrees(missile.getLocation(), targetLoc),
287 10000);
288 //AIUtils.turnTowardsPointV2(missile, targetLoc);
289 if (r > 0.5f) {
290 missile.giveCommand(ShipCommand.TURN_LEFT);
291 } else {
292 missile.giveCommand(ShipCommand.TURN_RIGHT);
293 }
294 missile.getEngineController().forceShowAccelerating();
295 }
296 }
297
298 tracker.advance(amount);
299 if (tracker.intervalElapsed()) {
300 if (elapsed >= 0.5f) {
302 }
303 //causeEnemyMissilesToTargetThis();
304 }
305 }
306
307
308 @SuppressWarnings("unchecked")
309 protected boolean isTargetValid() {
310 if (target == null || (target instanceof ShipAPI && ((ShipAPI)target).isPhased())) {
311 return false;
312 }
313 CombatEngineAPI engine = Global.getCombatEngine();
314
315 if (target != null && target instanceof ShipAPI && ((ShipAPI)target).isHulk()) return false;
316
317 List list = null;
318 if (target instanceof ShipAPI) {
319 list = engine.getShips();
320 } else {
321 list = engine.getMissiles();
322 }
323 return target != null && list.contains(target) && target.getOwner() != missile.getOwner();
324 }
325
326 protected void acquireNewTargetIfNeeded() {
327 if (data.attractorLock != null) {
328 target = data.attractorLock;
329 return;
330 }
331
332 CombatEngineAPI engine = Global.getCombatEngine();
333
334 // want to: target nearest missile that is not targeted by another two motes already
335 int owner = missile.getOwner();
336
337 int maxMotesPerMissile = 2;
338 float maxDistFromSourceShip = MoteControlScript.MAX_DIST_FROM_SOURCE_TO_ENGAGE_AS_PD;
340
341 float minDist = Float.MAX_VALUE;
342 CombatEntityAPI closest = null;
343 for (MissileAPI other : engine.getMissiles()) {
344 if (other.getOwner() == owner) continue;
345 if (other.getOwner() == 100) continue;
346 float distToTarget = Misc.getDistance(missile.getLocation(), other.getLocation());
347
348 if (distToTarget > minDist) continue;
349 if (distToTarget > 3000 && !engine.isAwareOf(owner, other)) continue;
350
351 float distFromAttractor = Float.MAX_VALUE;
352 if (data.attractorTarget != null) {
353 distFromAttractor = Misc.getDistance(other.getLocation(), data.attractorTarget);
354 }
355 float distFromSource = Misc.getDistance(other.getLocation(), missile.getSource().getLocation());
356 if (distFromSource > maxDistFromSourceShip &&
357 distFromAttractor > maxDistFromAttractor) continue;
358
359 if (getNumMotesTargeting(other) >= maxMotesPerMissile) continue;
360 if (distToTarget < minDist) {
361 closest = other;
362 minDist = distToTarget;
363 }
364 }
365
366 for (ShipAPI other : engine.getShips()) {
367 if (other.getOwner() == owner) continue;
368 if (other.getOwner() == 100) continue;
369 if (!other.isFighter()) continue;
370 float distToTarget = Misc.getDistance(missile.getLocation(), other.getLocation());
371 if (distToTarget > minDist) continue;
372 if (distToTarget > 3000 && !engine.isAwareOf(owner, other)) continue;
373
374 float distFromAttractor = Float.MAX_VALUE;
375 if (data.attractorTarget != null) {
376 distFromAttractor = Misc.getDistance(other.getLocation(), data.attractorTarget);
377 }
378 float distFromSource = Misc.getDistance(other.getLocation(), missile.getSource().getLocation());
379 if (distFromSource > maxDistFromSourceShip &&
380 distFromAttractor > maxDistFromAttractor) continue;
381
382 if (getNumMotesTargeting(other) >= maxMotesPerMissile) continue;
383 if (distToTarget < minDist) {
384 closest = other;
385 minDist = distToTarget;
386 }
387 }
388
389 target = closest;
390 }
391
392 protected int getNumMotesTargeting(CombatEntityAPI other) {
393 int count = 0;
394 for (MissileAPI mote : data.motes) {
395 if (mote == missile) continue;
396 if (mote.getUnwrappedMissileAI() instanceof MoteAIScript) {
397 MoteAIScript ai = (MoteAIScript) mote.getUnwrappedMissileAI();
398 if (ai.getTarget() == other) {
399 count++;
400 }
401 }
402 }
403 return count;
404 }
405
406 public Vector2f getAttractorLoc() {
407 Vector2f attractor = null;
408 if (data.attractorTarget != null) {
409 attractor = data.attractorTarget;
410 if (data.attractorLock != null) {
411 attractor = data.attractorLock.getLocation();
412 }
413 }
414 return attractor;
415 }
416
417 public CombatEntityAPI getTarget() {
418 return target;
419 }
420
421 public void setTarget(CombatEntityAPI target) {
422 this.target = target;
423 }
424 public void render() {
425
426 }
427}
static CombatEngineAPI getCombatEngine()
Definition Global.java:63
int getNumMotesTargeting(CombatEntityAPI other)
static SharedMoteAIData getSharedData(ShipAPI source)