public class fed_TravelDriveStats extends BaseShipSystemScript {
public static Logger log = Global.getLogger(fed_TravelDriveStats.class);
@Override
public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
//ShipAPI ship = null;
CombatEngineAPI engine = Global.getCombatEngine();
if (engine == null) {
return;
}
if (engine.isPaused()) {
//return;
}
Boolean validPlayerShip = false;
// We can get ship APIs from a MutableShipStatAPI using getEntity()
if (stats.getEntity() instanceof ShipAPI) { // Needed? maybe not, but I didn't touch bc im lazy
ShipAPI ship = (ShipAPI) stats.getEntity();
// Magic-ish number, will use later to make escaping fleets spawn inside the map (if they are far OoB, they autoescape)
float retreatNerfMult = 0.05f;
// We want ships to warp in from offscreen when deployed
// We only want do this for newly deployed ships once.
// So we will use some custom data (or lack thereof) to make sure the move-back is a one-off
// Reminder: ship_systems.csv matters a lot to this travel drive. It runs on strict 20sec intervals
// so that unfortunately means there are a few magic numbers here that mostly make it work as intended.
// literally ill-advised modifications hullmod, but in IRL
// Look for custom data tag to prevent double move-backs.
// We will insert it once we move the ship back, but simply run stat changes/gfx if we see it.
if (!ship.getCustomData().containsKey(id)) {
// DEBUG
/*log.info(
"Moving back - Ship ID: " + ship.getId()
+ " | Ship FMID: " + ship.getFleetMemberId()
+ " | Ship TimeDeployed: " + ship.getFullTimeDeployed()
+ " | Ship Name: " + ship.getName());
for ( Object value : ship.getCustomData().values()) {
log.info(value.toString());
}
log.info("Esc Dburn Dur " + engine.getContext().getEscapeDeploymentBurnDuration());
log.info("Flank Dburn Dist " + engine.getContext().getFlankDeploymentDistance());
log.info("Init Dburn Dur " + engine.getContext().getInitialDeploymentBurnDuration());
log.info("Init Esc Range " + engine.getContext().getInitialEscapeRange());
log.info("Init Num Step " + engine.getContext().getInitialNumSteps());
log.info("Init Step Size " + engine.getContext().getInitialStepSize());
log.info("Stdoff range " + engine.getContext().getStandoffRange());
log.info("Norm Dburn Dur " + engine.getContext().getNormalDeploymentBurnDuration());
log.info("Other goal " + engine.getContext().getOtherGoal());
log.info("player goal " + engine.getContext().getPlayerGoal());*/
// Look for burn duration for new ships depending on battle context.
// It appears ships don't ALWAYS burn in from offscreen, ex) they are being pursued and start in the lower area
// or are pushed up to reinforce a station. Still WIP determination through playtesting.
// First magic number. Gotta test via debugging printing log messages in all the various combat situations.
// I think it's usually 1.5f? NormalDeploymentBurn is 6, and EscapeDeploymentBurn is 1.5 as well.
// Don't quote me on that though
float initalBurnSeconds = engine.getContext().getInitialDeploymentBurnDuration();
// Second magic number, correlating with ship_system.csv's ACTIVE time for this travel drive.
// Don't ask me why or how it works. Because it kinda doesn't if put under real scrutiny.
float penalty = 10000f - engine.getMapHeight() * 0.03f;
// So we need to deal with ships that sometimes CANNOT be moved offscreen, lest they auto-retreat
// Thus we start with the player escaping.
if (engine.getContext().getPlayerGoal() != null && engine.getContext().getPlayerGoal().equals(FleetGoal.ESCAPE)) {
// Magic number *** for NPC ships, don't make pursing starfed AI hostiles start too close yknow?
penalty = penalty - (1000f + engine.getMapHeight() * 0.03f);
// But here we make player warp-ins start much closer, but move much slower.
// This means they don't run off the map but still work with the 20 second ACTIVE time defined in the csv.
if (ship.getOwner() == engine.getPlayerShip().getOwner() || ship.isAlly()) {
retreatNerfMult = 0.3333333333f;
initalBurnSeconds = engine.getContext().getEscapeDeploymentBurnDuration() * 0.15f;
stats.getMaxSpeed().modifyMult(id, retreatNerfMult);
//Probably the most unreliable of magic numbers. Doesn't always place it on-point in retreating formations.
penalty = penalty * 0.4f - (1200f - engine.getMapHeight() * 0.05f);
}
}
// CASE: Enemy fleet is escaping. We can't move them too far back off the map or they autoretreat.
if (engine.getContext().getOtherGoal() != null && engine.getContext().getOtherGoal().equals(FleetGoal.ESCAPE)) {
// Wohoo magic numbers
initalBurnSeconds = engine.getContext().getEscapeDeploymentBurnDuration() * 0.15f;
if (ship.getOwner() == engine.getPlayerShip().getOwner() || ship.isAlly()) {
// What I determined to be the distance that friendly ships will burn to
// Even if starfed warping ships rushing past might make them stop halfway. Oops.
penalty = penalty - (2000f + engine.getMapHeight() * 0.05f);
} else {
// this makes them really slow so they can start kinda on the map and not auto retreat.
retreatNerfMult = 0.16666666666f;
initalBurnSeconds = engine.getContext().getEscapeDeploymentBurnDuration() * 0.15f;
stats.getMaxSpeed().modifyMult(id, retreatNerfMult);
// A magic number I determined would make them start relatively fairly, like normal retreating ships.
penalty = penalty * 0.2f - (1200f - engine.getMapHeight() * 0.03f);
}
}
// We need to know how theoretically far back these ships are moving.
// So we calcualte the burn time * the mostly-hard speed limit of combat entities, not accounting for time mults.
float burnDistanceProbablyCovered = ((initalBurnSeconds * 610f));
// Double that for our new burn time, as we will be moving back an additional distance.
// Honestly these numbers are messed up or don't work at all...
// They all rely on the 20 second ACTIVE time in the ship_systems.csv, you can't really
// So you can't make ship travel drives turn on a set amount of time I think.
float newTimeToBurn = ((initalBurnSeconds));
// log.info("burnDistanceProbablyCovered " + burnDistanceProbablyCovered);
// log.info("penalty " + penalty);
// Now we determine what direciton we need to shift the ship "backwards", depending on heading.
// Pretty sure the center of the map is 0,0 in terms of X, Y, with -X being the left and -Y being the south.
// You can probably do this moveback purely by manipulating vectors... but I am not smart.
if (ship.getFacing() == 0f) {
// Facing 0 means facing to the left, so we add more X value to make it slide right.
ship.getLocation().setX(ship.getLocation().getX() - burnDistanceProbablyCovered - (penalty * 1.05f));
ship.turnOnTravelDrive(newTimeToBurn);
} else if (ship.getFacing() == 90f) {
// Facing 90 means facing upwards, so we reduce Y value to make the ship go lower
ship.getLocation().setY(ship.getLocation().getY() - burnDistanceProbablyCovered - penalty * 0.98f);
ship.turnOnTravelDrive();
} else if (ship.getFacing() == 180f) {
// Facing 180 means facing to the right, so we reduce the X value to make it slide left.
ship.getLocation().setX(ship.getLocation().getX() + burnDistanceProbablyCovered + (penalty * 1.05f));
ship.turnOnTravelDrive(newTimeToBurn);
} else if (ship.getFacing() == 270f) {
// Facing 270 means facing downards, so we add more Y value to make the ship go higher.
ship.getLocation().setY(ship.getLocation().getY() + burnDistanceProbablyCovered + penalty * 0.98f );
ship.turnOnTravelDrive(newTimeToBurn);
} else {
// If a ship is NOT in a designated facing, make it leave warp via the OUT state.
ship.getTravelDrive().forceState(ShipSystemAPI.SystemState.OUT, 0f);
}
//}
// Moving and warping is done, so now we add the data value to ensure it doesn't get moved back again.
// The TRUE value is used to signify the ship has been moved back out of the map.
// A FALSE value in this custom data entry will prevent a second moveback given the customData exists at all, BUT...
// we will assume that FALSE means it has left warp, and FALSE can only be gained from the OUT function.
// This entire complexity is because those allied ships you can reinforce in battle will have ACTIVE traveldrives
// that make them invisible and non-collidable. Unfortunately these ghost ships will remain ghosts because
// they refuse to enter the OUT stage unless the ACTIVE code has an exit somehow... hence the initial TRUE
// value indicating that the ship has not "left" warping and should probably leave via being forced into the OUT
// stage, thereby becoming tangible again and gaining a FALSE data value.
ship.setCustomData(id, true);
}
// Do not keep doing graphics/stat mod stuff when the screen is paused
if (engine.isPaused()) {
return;
}
if (null != state) {
switch (state) {
case IN: // UNUSED, probably, ship has no "in" time definied in shipsystems.csv
ship.setCustomData(id, true);
ship.setAlphaMult(1 - 1 * effectLevel);
ship.setCollisionClass(CollisionClass.FIGHTER);
// If a ship has modules, we need it to fade out/disable fleetshields too.
if (ship.isShipWithModules()) {
List<ShipAPI> modules = ship.getChildModulesCopy();
for (ShipAPI childModule : modules) {
childModule.setCollisionClass(CollisionClass.FIGHTER);
// Fookin null checks
if (childModule.getShield() != null) {
if (childModule.getShield().getType() == (ShieldAPI.ShieldType.FRONT) || (childModule.getShield().getType() == ShieldAPI.ShieldType.OMNI)) {
// Overkill never fails
childModule.getShield().setArc(0f);
childModule.getShield().setRadius(0f);
}
}
childModule.setAlphaMult(1 - 0.8f * effectLevel);
}
}
// Gee, how come mom lets you have three explosions instead of one!
// ... because I am a master of gfx, that's why.
fed_spawnTravelDriveExplosion(stats, ship, effectLevel);
fed_spawnTravelDriveExplosion(stats, ship, effectLevel);
fed_spawnTravelDriveExplosion(stats, ship, effectLevel);
break;
case ACTIVE:
// Hard to get allies already deployed ( IE you are reinforcing an existing battle ) out of this state.
// The if statement immediately below tries to solve that, it disqualifies most ships from using TD.
// But you can use the warp drive if retreating.
if (ship.getCustomData().get(id).equals(true) && !ship.isRetreating() && ship.getFullTimeDeployed() > 20f) {
ship.setCustomData(id, false);
effectLevel = 0;
ship.getTravelDrive().forceState(ShipSystemAPI.SystemState.OUT, 0f);
}
// Just make sure we know the ship has warped, cannot tell if it goes through logic above or not.
ship.setCustomData(id, true);
// Make ship not bonk things when warping.
ship.setAlphaMult(1 - 0.9f * effectLevel);
ship.setPhased(true);
ship.setCollisionClass(CollisionClass.FIGHTER);
// Zoom
stats.getMaxSpeed().modifyFlat(id, 600f);
stats.getAcceleration().modifyFlat(id, 600f);
// Only way to make it seem literally FTL, like tesseracts engaging temporal shell
stats.getTimeMult().modifyPercent(id, 600 * effectLevel);
// Some prerandomized variables to argument size in cosmetic particle calls
// RENDER PARTICLES FOR WARPING SHIPS
/*ship.addAfterimage(
new Color(125, 125, 1, 255), //color
MathUtils.getRandomNumberInRange(-5f, 5f), //X Location rel to ship center
MathUtils.getRandomNumberInRange(-5f, 5f), //Y Location rel to ship center
(stats.getEntity().getVelocity().getX()) * -1f, //X Velocity
(stats.getEntity().getVelocity().getY()) * -1f, //Y Velocity
5f, //Max Jitter, units of distance
0f, //Fade-in time
0f, //Regular duration
0.5f, //Fade-out time
true, //Is Additive (whiteness adds up)
true, //Combine with sprite color
true); //Above ship
*/
// GD modules need afterimages/no hitbox too waaaah
if (ship.isShipWithModules()) {
List<ShipAPI> modules = ship.getChildModulesCopy();
for (ShipAPI childModule : modules) {
childModule.addAfterimage(
new Color(125, 100, 25, 55), //color
MathUtils.getRandomNumberInRange(-5f, 5f), //X Location rel to ship center
MathUtils.getRandomNumberInRange(-5f, 5f), //Y Location rel to ship center
(stats.getEntity().getVelocity().getX()) * -1f, //X Velocity
(stats.getEntity().getVelocity().getY()) * -1f, //Y Velocity
5f, //Max Jitter, units of distance
0f, //Fade-in time
0f, //Regular duration
0.2f, //Fade-out time
true, //Is Additive (whiteness adds up)
true, //Combine with sprite color
true);
childModule.setCollisionClass(CollisionClass.FIGHTER);
// Morrrre null checks
if (childModule.getShield() != null) {
if (childModule.getShield().getType() == (ShieldAPI.ShieldType.FRONT) || (childModule.getShield().getType() == ShieldAPI.ShieldType.OMNI)) {
// Overkill
childModule.getShield().setArc(0f);
childModule.getShield().setRadius(0f);
}
}
childModule.setAlphaMult(1 - 0.8f * effectLevel);
}
}
// We don't always want to render the warp trail, keeps it lighter
fed_spawnTravelDriveWake(stats, ship, effectLevel);
break;
case OUT:
// Hard to trigger this unless forced when with exisiting battles with allies you are reinforcing.
// See first lines of code in ACTIVE state contents above.
ship.setCustomData(id, false);
ship.setPhased(false);
stats.getMaxSpeed().unmodify(id);
float shipExitSpeed = (stats.getDeceleration().modified) * 10 + ship.getMutableStats().getZeroFluxSpeedBoost().modified;
//engine.addFloatingText(ship.getLocation(), "Y: " + ship.getLocation().y, 15f, Color.RED, ship, 1f, 0.5f);
if (shipExitSpeed > 500f) {
shipExitSpeed = 500f;
}
if (ship.getHullSize().equals(ShipAPI.HullSize.CAPITAL_SHIP)) {
shipExitSpeed = (220f);
}
ship.getVelocity().scale(shipExitSpeed / (ship.getVelocity().length()));
stats.getTimeMult().unmodify(id);
ship.setAlphaMult(1 - 0.8f * effectLevel);
// Give modules back shields as we come out of warp.
//if (ship.getVelocity().length() <= ship.getMaxSpeed()) { //&& ship.getFullTimeDeployed() > 1) {
if (ship.isShipWithModules()) {
List<ShipAPI> modules = ship.getChildModulesCopy();
for (ShipAPI childModule : modules) {
if (!childModule.getVariant().hasHullMod("fed_fleetshieldeffect")) {
childModule.setCollisionClass(CollisionClass.SHIP);
}
childModule.ensureClonedStationSlotSpec();
if (childModule.getShield() != null) {
if (childModule.getShield().getType() == (ShieldAPI.ShieldType.FRONT) || (childModule.getShield().getType() == ShieldAPI.ShieldType.OMNI)) {
childModule.getShield().setType(childModule.getHullSpec().getShieldType());
childModule.getShield().setArc(childModule.getHullSpec().getShieldSpec().getArc());
// This doesn't nuke shields because FederationDesign.java sets shield radiuses constantly.
childModule.getShield().setRadius(0f);
}
}
childModule.setAlphaMult(1f);
}
}
stats.getEntity().setCollisionClass(CollisionClass.SHIP);
ship.getTravelDrive().deactivate();
fed_spawnTravelDriveExplosion(stats, ship, effectLevel);
fed_spawnTravelDriveExplosion(stats, ship, effectLevel);
fed_spawnTravelDriveExplosion(stats, ship, effectLevel);
// Immediate sound based on nearby arrivals
Global.getSoundPlayer().playSound(ship.getTravelDrive().getSpecAPI().getOutOfUsesSound(), 1, 1, ship.getLocation(), new Vector2f(0f, 0f));
// Faint UI sound for distant arrivals
Global.getSoundPlayer().playUISound(ship.getTravelDrive().getSpecAPI().getOutOfUsesSound(), 1, 0.1f);
// }
break;
default: //Unused
break;
}
}
/* DEBUGGING
log.info(
" | Ship Name: " + ship.getName()
+ " | Ship TD State: " + ship.getTravelDrive().getState()
+ " | Ship Effect Level: " + ship.getTravelDrive().getEffectLevel()
+ " | Ship TimeDeployed: " + ship.getFullTimeDeployed()
+ " | Combat Time Elapsed: " + engine.getTotalElapsedTime(false)
+ " HAS DATA: " + ship.getCustomData().containsKey(id)
);*/
}
}
@Override
public void unapply(MutableShipStatsAPI stats, String id
) {
stats.getTimeMult().unmodify(id);
stats.getMaxSpeed().unmodify(id);
stats.getAcceleration().unmodify(id);
stats.getDeceleration().unmodify(id);
stats.getMaxTurnRate().unmodify(id);
stats.getMaxTurnRate().unmodify(id);
stats.getTurnAcceleration().unmodify(id);
stats.getTurnAcceleration().unmodify(id);
Global.getCombatEngine().getTimeMult().unmodify(id);
}
}