Starsector API
Loading...
Searching...
No Matches
NeuralLinkScript.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.skills;
2
3import java.awt.Color;
4import java.util.ArrayList;
5import java.util.LinkedHashMap;
6import java.util.List;
7import java.util.Map;
8
9import org.lwjgl.input.Mouse;
10
11import com.fs.starfarer.api.Global;
12import com.fs.starfarer.api.characters.PersonAPI;
13import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
14import com.fs.starfarer.api.combat.CombatEngineAPI;
15import com.fs.starfarer.api.combat.CombatFleetManagerAPI;
16import com.fs.starfarer.api.combat.DeployedFleetMemberAPI;
17import com.fs.starfarer.api.combat.ShipAIPlugin;
18import com.fs.starfarer.api.combat.ShipAPI;
19import com.fs.starfarer.api.combat.ShipCommand;
20import com.fs.starfarer.api.combat.ViewportAPI;
21import com.fs.starfarer.api.combat.WeaponGroupAPI;
22import com.fs.starfarer.api.impl.campaign.ids.Stats;
23import com.fs.starfarer.api.impl.hullmods.NeuralInterface;
24import com.fs.starfarer.api.input.InputEventAPI;
25import com.fs.starfarer.api.loading.WeaponGroupSpec;
26import com.fs.starfarer.api.mission.FleetSide;
27
28public class NeuralLinkScript extends BaseEveryFrameCombatPlugin {
29 public static String TRANSFER_CONTROL = "SHIP_TOGGLE_XPAN_MODE";
30
31 public static float INSTANT_TRANSFER_DP = 50;
32 public static float TRANSFER_SECONDS_PER_DP = 0.125f;
33 public static float TRANSFER_MAX_SECONDS = 5f;
34 public static float TRANSFER_MIN_SECONDS_IF_NOT_INSTANT = 1f;
35
36 // doesn't really work out - disables shields etc, and you also can't just
37 // switch to see how the ship is doing without really disrupting it
38 public static boolean ALLOW_ENGINE_CONTROL_DURING_TRANSFER = false;
39
40 public static final Object KEY_STATUS = new Object();
41 public static final Object KEY_STATUS2 = new Object();
42
43 public static final String TRANSFER_COMPLETE_KEY = "neural_transfer_complete_key";
44
45
46 public static class SavedShipControlState {
47 public ShipAPI ship;
48 public Map<WeaponGroupAPI, Boolean> autofiring = new LinkedHashMap<WeaponGroupAPI, Boolean>();
49 public WeaponGroupAPI selected;
50 }
51
52
53
54 protected CombatEngineAPI engine;
55 public void init(CombatEngineAPI engine) {
56 this.engine = engine;
57 }
58
59 protected ShipAPI prevPlayerShip = null;
60 protected int skipFrames = 0;
61
62 protected List<ShipAPI> linked = new ArrayList<ShipAPI>();
63
64 protected float untilTransfer;
65 protected int lastShownTime = 0;
66
67 protected SavedShipControlState prevState;
68 protected SavedShipControlState savedState;
69
70 protected PersonAPI playerPerson;
71
72 public void saveControlState(ShipAPI ship) {
74 savedState = new SavedShipControlState();
75 savedState.ship = ship;
76 for (WeaponGroupAPI group : ship.getWeaponGroupsCopy()) {
77 savedState.autofiring.put(group, group.isAutofiring());
78 }
79 savedState.selected = ship.getSelectedGroupAPI();
80 }
81 public void restoreControlState(ShipAPI ship) {
82 if (ship == null) {
83 return;
84 }
85
86 if (prevState == null || prevState.ship != ship) {
87 List<WeaponGroupAPI> groups = ship.getWeaponGroupsCopy();
88 int index = 0;
89 for (WeaponGroupSpec groupSpec : ship.getVariant().getWeaponGroups()) {
90 if (index >= groups.size()) break;
91
92 boolean auto = groupSpec.isAutofireOnByDefault();
93 WeaponGroupAPI group = groups.get(index);
94 if (group != null) {
95 if (auto) {
96 group.toggleOn();
97 } else {
98 group.toggleOff();
99 }
100 }
101 index++;
102 }
103 if (groups.size() >= 1) {
104 ship.giveCommand(ShipCommand.SELECT_GROUP, null, 0);
105 }
106 return;
107 }
108
109
110 for (WeaponGroupAPI group : ship.getWeaponGroupsCopy()) {
111 Boolean auto = prevState.autofiring.get(group);
112 if (auto == null) auto = false;
113 if (auto) {
114 group.toggleOn();
115 } else {
116 group.toggleOff();
117 }
118 }
119 int index = ship.getWeaponGroupsCopy().indexOf(prevState.selected);
120 if (index > 0) {
121 ship.giveCommand(ShipCommand.SELECT_GROUP, null, index);
122 }
123 }
124
125 public void advance(float amount, List<InputEventAPI> events) {
126 if (engine == null) return;
127 if (engine.isPaused()) return;
128
129
130 ShipAPI playerShip = engine.getPlayerShip();
131 if (playerShip == null) {
132 return;
133 }
134
135 if (!playerShip.isAlive()) {
136 untilTransfer = 0f;
137 lastShownTime = 0;
138 }
139
140 if (untilTransfer > 0) {
141 float timeMult = playerShip.getMutableStats().getTimeMult().getModifiedValue();
142 untilTransfer -= amount * timeMult;
143
145// Global.getSoundPlayer().applyLowPassFilter(1f - (1f - spec.getFilterGain()) * level * mult,
146// 1f - (1f - spec.getFilterGainHF()) * level * mult);
147 //engine.getCombatUI().setShipInfoFanOutBrightness(0f);
148 engine.getCombatUI().hideShipInfo();
149 if (untilTransfer <= 0) {
150 untilTransfer = 0;
151 Global.getSoundPlayer().playUISound("ui_neural_transfer_complete", 1f, 1f);
152 playerShip.setCustomData(TRANSFER_COMPLETE_KEY, true);
154 engine.getCombatUI().reFanOutShipInfo();
155 boolean autopilot = engine.getCombatUI().isAutopilotOn();
156 if (autopilot) {
157 if (playerShip.getAI() == null) { // somehow?
158 CombatFleetManagerAPI manager = engine.getFleetManager(FleetSide.PLAYER);
159 DeployedFleetMemberAPI member = manager.getDeployedFleetMember(playerShip);
160 playerShip.setShipAI(Global.getSettings().pickShipAIPlugin(member == null ? null : member.getMember(), playerShip));
161 }
162 } else {
163 playerShip.setShipAI(null);
164 restoreControlState(playerShip);
165 }
166 } else {
169 }
170 }
171
172 // if the player changed flagships:
173 // skip a few frames to make sure the status ends up on top of the status list
174 if (playerShip != prevPlayerShip) {
175 prevPlayerShip = playerShip;
176 skipFrames = 30;
177 }
178
179 if (skipFrames > 0) {
180 skipFrames--;
181 return;
182 }
183
185 }
186
187 public void suppressControlsDuringTransfer(ShipAPI playerShip) {
189 playerShip.blockCommandForOneFrame(ShipCommand.FIRE);
190 playerShip.blockCommandForOneFrame(ShipCommand.TOGGLE_AUTOFIRE);
191 playerShip.blockCommandForOneFrame(ShipCommand.PULL_BACK_FIGHTERS);
192 playerShip.blockCommandForOneFrame(ShipCommand.VENT_FLUX);
193 playerShip.blockCommandForOneFrame(ShipCommand.USE_SELECTED_GROUP);
194 playerShip.blockCommandForOneFrame(ShipCommand.USE_SYSTEM);
195 playerShip.blockCommandForOneFrame(ShipCommand.HOLD_FIRE);
196 } else {
197 engine.getCombatUI().setDisablePlayerShipControlOneFrame(true);
198 }
199 }
200
202 ShipAPI playerShip = engine.getPlayerShip();
203 if (playerShip == null) return;
204 float timeMult = playerShip.getMutableStats().getTimeMult().getModifiedValue();
205 Color color = new Color(0,121,216,255);
206
207// feels kind of annoying
208// if (untilTransfer <= 0) {
209// engine.addFloatingTextAlways(playerShip.getLocation(), "Transfer complete",
210// getFloatySize(playerShip), color, playerShip, 5f * timeMult, 2f, 1f/timeMult, 0f, 0f,
211// 1f);
212// return;
213// }
214
215 int show = (int) Math.ceil(untilTransfer);
216 if (show != lastShownTime) {
217 if (show > 0) {
218 engine.addFloatingTextAlways(playerShip.getLocation(), "Neural transfer in " + show,
219 getFloatySize(playerShip), color, playerShip, 4f * timeMult, 0.8f/timeMult, 1f/timeMult, 0f, 0f,
220 1f);
221 }
222 lastShownTime = show;
223 //Global.getSoundPlayer().playUISound("ui_hold_fire_on", 2f, 0.25f);
224 }
225 }
226
227 public boolean canLink(ShipAPI ship) {
228 // below line: debug, work for every ship
229 //if (ship.isAlive() && !ship.isShuttlePod()) return true;
230
231 ShipAPI playerShip = engine.getPlayerShip();
232 // transferred command to officer'ed ship, can't link
233 if (ship == playerShip && ship.getOriginalCaptain() != null &&
234 !ship.getOriginalCaptain().isDefault() && !ship.getOriginalCaptain().isPlayer()) {
235 return false;
236 }
237
238 if (engine.isInCampaign() || engine.isInCampaignSim()) {
239 if (Global.getSector().getPlayerStats().getDynamic().getMod(Stats.HAS_NEURAL_LINK).computeEffective(0f) <= 0) {
240 return false;
241 }
242 }
243
244 boolean aliveOrDisabledButNonPhysical = ship.isAlive();
245 ShipAPI physicalLocation = engine.getShipPlayerLastTransferredCommandTo();
246 if (ship == playerShip && ship != physicalLocation) {
247 aliveOrDisabledButNonPhysical = true;
248 }
249
250 return aliveOrDisabledButNonPhysical && !ship.isShuttlePod() &&
251 ship.getMutableStats().getDynamic().getMod(Stats.HAS_NEURAL_LINK).computeEffective(0f) > 0;
252 //ship.getVariant().hasHullMod(HullMods.NEURAL_INTERFACE);
253 }
254
255 public void updateLinkState() {
256 ShipAPI playerShip = engine.getPlayerShip();
257 if (playerShip == null) return;
258
259 ShipAPI physicalLocation = engine.getShipPlayerLastTransferredCommandTo();
260 for (ShipAPI ship : new ArrayList<ShipAPI>(linked)) {
261 if (!ship.isAlive()) {
262 if (ship == playerShip && ship != physicalLocation) {
263 continue;
264 }
265
266 if (ship != playerShip) {
267 PersonAPI orig = ship.getOriginalCaptain();
268 if (orig.isPlayer()) {
269 orig = Global.getFactory().createPerson();
270 if (engine.isInCampaign() || engine.isInCampaignSim()) {
271 orig.setPersonality(Global.getSector().getPlayerFaction().pickPersonality());
272 }
273 }
274 ship.setCaptain(orig);
275 }
276 linked.remove(ship);
277 }
278 }
279
280 boolean physicallyPresent = linked.contains(physicalLocation);
281 if (!linked.contains(playerShip) || !physicallyPresent ||
282 !canLink(playerShip)) {
283 for (ShipAPI ship : linked) {
284 PersonAPI orig = ship.getOriginalCaptain();
285 if (orig.isPlayer()) {
286 orig = Global.getFactory().createPerson();
287 if (engine.isInCampaign() || engine.isInCampaignSim()) {
288 orig.setPersonality(Global.getSector().getPlayerFaction().pickPersonality());
289 }
290 }
291 if (ship.getCaptain() != orig) {
292 ship.setCaptain(orig);
293 if (ship.getFleetMember() != null) {
294 ship.getFleetMember().setCaptain(ship.getOriginalCaptain());
295 }
296 }
297 }
298 linked.clear();
299 if (canLink(playerShip)) {
300 linked.add(playerShip);
301 }
302 }
303
304 if (linked.isEmpty()) return;
305
306
307 CombatFleetManagerAPI manager = engine.getFleetManager(FleetSide.PLAYER);
308 List<DeployedFleetMemberAPI> members = manager.getDeployedCopyDFM();
309
310 if (physicallyPresent) {
311 for (DeployedFleetMemberAPI dfm : members) {
312 if (linked.size() >= 2) break;
313
314 if (dfm.isFighterWing()) continue;
315 if (dfm.isAlly()) continue;
316
317 ShipAPI ship = dfm.getShip();
318 if (linked.contains(ship)) continue;
319 if (!ship.getCaptain().isDefault() && ship != playerShip &&
320 !ship.getCaptain().isPlayer()) {
321 // this last for when the player deploys a ship with NI in the simulator and then
322 // deploys their actual flagship - so, the player ship doesn't *actually* have the player in it
323 // but the other ship does
324 continue;
325 }
326
327 // transferred command to an officer'ed ship, no link
328 if (ship == playerShip && ship.getOriginalCaptain() != null &&
329 !ship.getOriginalCaptain().isDefault() &&
330 !ship.getOriginalCaptain().isPlayer()) {
331 continue;
332 }
333 if (ship.controlsLocked()) continue;
334
335 if (canLink(ship)) {
336 linked.add(ship);
337 }
338 }
339 }
340
341 PersonAPI player = playerPerson;
342 if (player == null) {
343 player = playerShip.getCaptain();
344 if (!player.isDefault() && playerPerson == null) {
345 playerPerson = player;
346 }
347 }
348
349 for (ShipAPI ship : linked) {
350 if (ship.getCaptain() != player) {
351 ship.setCaptain(player);
352 if (ship.getFleetMember() != null) {
353 ship.getFleetMember().setCaptain(player);
354 }
355 }
356 }
357
358 if (linked.contains(playerShip)) {
359 ShipAPI other = null;
360 for (ShipAPI ship : linked) {
361 if (ship != playerShip) {
362 other = ship;
363 break;
364 }
365 }
366
367 String title = "Neural System Reset";
368 //String title = "System Reset on Transfer";
369 String icon = Global.getSettings().getSpriteName("ui", "icon_neural_link");
370 String key = NeuralInterface.SYSTEM_RESET_TIMEOUT_KEY;
371// Float timeout = (Float) Global.getCombatEngine().getCustomData().get(key);
372// if (timeout == null) timeout = 0f;
373 Float timeout = null;
374 if (other != null) timeout = (Float) other.getCustomData().get(key);
375 if (timeout == null) timeout = 0f;
376 if (other == null) {
377 engine.maintainStatusForPlayerShip(KEY_STATUS2, icon, title, "No signal", true);
378 } else if (timeout <= 0) {
379 engine.maintainStatusForPlayerShip(KEY_STATUS2, icon, title, "Ready on transfer", false);
380 } else {
381 int show = (int) Math.ceil(timeout);
382 engine.maintainStatusForPlayerShip(KEY_STATUS2, icon, title, "Ready in " + show + " seconds", true);
383 }
384 }
385
386 for (ShipAPI ship : linked) {
387 if (ship != playerShip) {
388
389 if (untilTransfer <= 0f) {
390 String title = "Neural Link Active";
391 //String data = ship.getName() + ", " + ship.getHullSpec().getHullNameWithDashClass();
392 //String data = ship.getName() + ", " + ship.getHullSpec().getHullName();
393 String data = "Target: " + ship.getHullSpec().getHullNameWithDashClass();
394 String icon = Global.getSettings().getSpriteName("ui", "icon_neural_link");
395 engine.maintainStatusForPlayerShip(KEY_STATUS, icon, title, data, false);
396 } else {
397 int show = (int) Math.ceil(untilTransfer);
398 if (show > 0) {
399 String title = "Neural Transfer";
400 String data = "Link in " + show + " seconds";
401 String icon = Global.getSettings().getSpriteName("ui", "icon_neural_link");
402 engine.maintainStatusForPlayerShip(KEY_STATUS, icon, title, data, true);
403 }
404 }
405
406 break;
407 }
408 }
409
410 if (linked.size() <= 1 && linked.contains(playerShip)) {
411 String title = "Neural Link Inactive";
412 String data = "No signal";
413 if (!physicallyPresent) {
414 data = "requires physical transfer";
415 }
416 String icon = Global.getSettings().getSpriteName("ui", "icon_neural_link");
417 engine.maintainStatusForPlayerShip(KEY_STATUS, icon, title, data, true);
418 }
419 }
420
421
422 public void processInputPreCoreControls(float amount, List<InputEventAPI> events) {
423 if (engine == null || engine.getCombatUI() == null || engine.getCombatUI().isShowingCommandUI()) return;
424
425 ShipAPI playerShip = engine.getPlayerShip();
426 if (playerShip == null) return;
427 if (!linked.contains(playerShip) || linked.size() < 2) return;
428 //if (untilTransfer > 0) return;
429
430 for (InputEventAPI event : events) {
431 if (event.isConsumed()) continue;
432
433 if (event.isControlDownEvent(TRANSFER_CONTROL)) {
434 if (untilTransfer <= 0) {
435 for (ShipAPI ship : linked) {
436 if (ship != playerShip) {
438 lastShownTime = 0;
439 doTransfer(ship);
440 }
441 }
442 }
443 event.consume();
444 return;
445 }
446 }
447 }
448
449
450 public void doTransfer(ShipAPI ship) {
451 if (ship == null) return;
452 ShipAPI playerShip = engine.getPlayerShip();
453 if (playerShip == null) return;
454 if (!linked.contains(playerShip)) return;
455 if (!linked.contains(ship)) return;
456
457 if (untilTransfer <= 0) {
458 Global.getSoundPlayer().playUISound("ui_neural_transfer_complete", 1f, 1f);
459 ship.setCustomData(TRANSFER_COMPLETE_KEY, true);
461 engine.getCombatUI().reFanOutShipInfo();
462 } else {
463 Global.getSoundPlayer().playUISound("ui_neural_transfer_begin", 1f, 1f);
464 }
465
466 // I have it on good authority that this looks bad.
467 // "[it looks like] some vague energy field effect, then like Troi would have a headache and some dubious writing would occur"
468// float animDur = Math.max(untilTransfer, 0.5f);
469// engine.addLayeredRenderingPlugin(new NeuralTransferVisual(playerShip, ship, animDur));
470
471 Mouse.setCursorPosition((int)Global.getSettings().getScreenWidthPixels()/2,
473
474 saveControlState(playerShip);
475
476 ShipAIPlugin playerShipAI = playerShip.getShipAI(); // non-null if autopilot is on
477 ShipAIPlugin prevTargetAI = ship.getShipAI();
478 engine.setPlayerShipExternal(ship);
479 if (ship.getFleetMember() != null) {
480 ship.getFleetMember().setCaptain(playerShip.getCaptain());
481 }
482
483 if (playerShipAI != null) {
484 playerShip.setShipAI(playerShipAI); // not the player ship anymore, the old ship
485 }
486
487 boolean autopilot = engine.getCombatUI().isAutopilotOn();
488
489 if (untilTransfer > 0) {
491 ship.setShipAI(prevTargetAI);
492 }
493 //engine.getCombatUI().setDisablePlayerShipControlOneFrame(true);
496 } else if (autopilot) {
497 CombatFleetManagerAPI manager = engine.getFleetManager(FleetSide.PLAYER);
498 DeployedFleetMemberAPI member = manager.getDeployedFleetMember(ship);
499 ship.setShipAI(Global.getSettings().pickShipAIPlugin(member == null ? null : member.getMember(), ship));
500 }
501
502 if (untilTransfer <= 0) {
504 if (!autopilot) {
506 }
507 }
508 }
509
510
511 public float getTransferTime() {
512 float total = 0f;
513 for (ShipAPI ship : linked) {
514 if (ship.getFleetMember() == null) continue;
515 total += ship.getFleetMember().getDeploymentPointsCost();
516 }
517
518 total = Math.round(total);
519
520 //INSTANT_TRANSFER_DP = 0f;
521
522 if (total <= INSTANT_TRANSFER_DP) return 0f;
523
524 float time = (total - INSTANT_TRANSFER_DP) * TRANSFER_SECONDS_PER_DP;
527 } else if (time > TRANSFER_MAX_SECONDS) {
529 }
530 time = (float) Math.ceil(time);
531
532 return time;
533 }
534
535
536 public void renderInUICoords(ViewportAPI viewport) {
537 }
538
539 public void renderInWorldCoords(ViewportAPI viewport) {
540 }
541
542 public static float getFloatySize(ShipAPI ship) {
543 switch (ship.getHullSize()) {
544 case FIGHTER: return 15f;
545 case FRIGATE: return 17f;
546 case DESTROYER: return 21f;
547 case CRUISER: return 24f;
548 case CAPITAL_SHIP: return 27f;
549 }
550 return 10f;
551 }
552}
static SettingsAPI getSettings()
Definition Global.java:51
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:43
static FactoryAPI getFactory()
Definition Global.java:35
static SectorAPI getSector()
Definition Global.java:59
void processInputPreCoreControls(float amount, List< InputEventAPI > events)
void advance(float amount, List< InputEventAPI > events)
String getSpriteName(String category, String id)
ShipAIPlugin pickShipAIPlugin(FleetMemberAPI member, ShipAPI ship)
void applyLowPassFilter(float gain, float gainHF)
SoundAPI playUISound(String id, float pitch, float volume)