Fractal Softworks Forum

Please login or register.

Login with username, password and session length

Author Topic: How to make playable fighters  (Read 3094 times)

shoi

  • Admiral
  • *****
  • Posts: 657
    • View Profile
How to make playable fighters
« on: November 17, 2021, 04:37:17 AM »

Generally speaking, there are two approaches to making a pilotable fighter. The easiest method is to simply give a frigate fighter collision. This method has a ton of issues associated with it, however. AI will still evaluate the ship as a frigate, leading to a multitude of odd behaviors:

- Large ships turning to face the fake fighter in lieu of other targets
- Carriers attempting to order bomber strikes against the fake fighter
- Interceptors not prioritizing the fake fighter
- Various fighter-oriented skills/abilities not affecting the fake fighter

On top of this, a ship designed this way will break many mod weapons that have alternate behavior based on the class of ship it is targetting.

The solution to these issues is giving the ship fighter HullSize, but that comes with its own issues. Any ship that retreats with this hullsize will immediately crash the game, and they cannot be ordered with the command UI. On top of that, there is some peculiar behavior in situations when ships like these are the last one s remaining in a battle.

So how do you circumvent these issues? This is the methodology I used in ArmaA:

1.) A hullmod that sets the ship to fighter hullsize during combat. Alters hullsize and recreates effects that don't occur with the use of fighter hullsize, as well as ensuring the ship is properly destroyed/disabled in combat

Code
package data.hullmods;

import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.ShipAPI.HullSize;
import com.fs.starfarer.api.impl.campaign.ids.HullMods;
import com.fs.starfarer.api.fleet.FleetMemberType;
import com.fs.starfarer.api.fleet.FleetMemberAPI;
import com.fs.starfarer.api.characters.*;

import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
import com.fs.starfarer.api.impl.campaign.ids.Skills;
import com.fs.starfarer.api.fleet.FleetGoal;
import com.fs.starfarer.api.mission.FleetSide;
import com.fs.starfarer.api.util.IntervalUtil;
import com.fs.starfarer.api.util.Misc;
import com.fs.starfarer.api.ui.Alignment;
import com.fs.starfarer.api.ui.TooltipMakerAPI;

import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.entities.AnchoredEntity;

import com.fs.starfarer.api.combat.EmpArcEntityAPI;

import data.scripts.util.MagicUI;

import data.scripts.ai.armaa_combat_docking_AI;

import org.lazywizard.lazylib.combat.CombatUtils;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.combat.DefenseUtils;
import org.lwjgl.util.vector.Vector2f;

import java.util.List;
import java.util.ArrayList;
import java.awt.Color;
import java.util.Random;

import data.scripts.util.armaa_utils;
import com.fs.starfarer.api.SettingsAPI;
import com.fs.starfarer.api.combat.listeners.AdvanceableListener;
import com.fs.starfarer.api.combat.listeners.DamageTakenModifier;
import com.fs.starfarer.api.combat.listeners.ApplyDamageResultAPI;
import com.fs.starfarer.api.combat.listeners.DamageListener;
import org.apache.log4j.Logger;
import com.fs.starfarer.api.combat.ShipwideAIFlags;
public class armaa_strikeCraft extends BaseHullMod {

private Color COLOR = new Color(0, 106, 0, 50);
private float DEGRADE_INCREASE_PERCENT = 100f;
public MagicUI ui;
private static final float RETREAT_AREA_SIZE = 2050f;
private IntervalUtil interval = new IntervalUtil(25f,25f);
private IntervalUtil textInterval = new IntervalUtil(8f,8f);
private Color TEXT_COLOR = new Color(55,155,255,255);
private final Color CORE_COLOR = new Color(200, 200, 200);
private final Color FRINGE_COLOR = new Color(255, 10, 10);
private final float Repair = 100, CR = 20; //Hullmod in game description.
private float HPPercent = 0.80f;
private float BaseTimer = .30f;
        private float CRmarker = 0.4f;
private float MISSILE_DAMAGE_THRESHOLD = 200f;
private boolean hasLanded;
public final ArrayList<String> landingLines_Good = new ArrayList<>();
public final ArrayList<String> landingLines_Fair = new ArrayList<>();
public final ArrayList<String> landingLines_Critical = new ArrayList<>();
public final ArrayList<String> landingLines_notPossible = new ArrayList<>();

{
//If CR is low, but otherwise no major damage
                landingLines_Good.add("\"Welcome aboard, sir.\"");
landingLines_Good.add("\"Looks like you got away pretty clean!\"");
landingLines_Good.add("\"Hopefully, you bag a few more kills today.\"");
landingLines_Good.add("\"All conditions good. Fuselage solid.\"");

//If taken heavy damage, but CR isn't too bad
landingLines_Critical.add("\"Glad you made it back alive, sir.\"");
landingLines_Critical.add("\"Looks like this may be a tough battle, sir.\"");
landingLines_Critical.add("\"Be careful flying while severely damaged. Even anti-air fire could bring you down.\"");


//If heavy damage and low CR

//Tried to find a carrier but could not
landingLines_notPossible.add("\"Dammit, nowhere to land!\"");
landingLines_notPossible.add("\"Could really use somewhere to land right now..\"");
landingLines_notPossible.add("\"Zilch on available carriers. Aborting refit!\"");
landingLines_notPossible.add("\"There's nowhere for me to resupply.\"");
}

public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id)
{
stats.getZeroFluxMinimumFluxLevel().modifyFlat(id, -1f); // -1 means boost never takes effect; 2 means always on
stats.getCRLossPerSecondPercent().modifyPercent(id, DEGRADE_INCREASE_PERCENT);
}

@Override
public void applyEffectsAfterShipCreation(ShipAPI ship, java.lang.String id)
{
//These listeners are used to get around some of the quirks of using Fighter HullSize

ship.addListener(new StrikeCraftDeathMod(ship));
ship.addListener(new StrikeCraftDamageListener(ship));
}


public boolean canRetreat(ShipAPI ship)
{
float mapWidth = Global.getCombatEngine().getMapWidth() / 2f, mapHeight = Global.getCombatEngine().getMapHeight() / 2f;
Vector2f rawLL = new Vector2f(-mapWidth, -mapHeight),rawUR = new Vector2f(mapWidth, mapHeight);
CombatEngineAPI engine = Global.getCombatEngine();
BattleCreationContext context = engine.getContext();
FleetGoal objective = context.getPlayerGoal();
boolean check = false;
Vector2f location = ship.getLocation();
// if player is escaping or I am an enemy
if(objective == FleetGoal.ESCAPE || ship.getOwner() == 1)
{
//I can retreat at the top of the map
if(location.getY() > rawUR.getY() - RETREAT_AREA_SIZE)
{
check = true;
}
}
//if player is escaping and I am an enemy, or if I am player
if(objective == FleetGoal.ESCAPE && ship.getOwner() == 1 || ship.getOwner() == 0)
{
//I can retreat at the bottom of the map
if(location.getY() < RETREAT_AREA_SIZE+rawLL.getY())
{
check = true;
}
}
return check;
}

//determines when AI pilots will return to carrier
public void getPilotPersonality(ShipAPI ship)
{
String personality = "steady";
if (ship.getCaptain() != null)
{
PersonAPI pilot = ship.getCaptain();
personality = pilot.getPersonalityAPI().getId().toString();
}
float dangerLevel = .50f;
switch (personality)
{
case "steady":
// do nothing, we will use the default
break;
case "reckless":
// we have a death wish
dangerLevel = .30f;
break;
case "aggressive":
dangerLevel = .40f;
break;
case "timid":
dangerLevel = .70f;
break;
case "cautious":
dangerLevel = .60f;
break;
default:
break;
}

Global.getCombatEngine().getCustomData().put("armaa_strikecraftPilot"+ship.getId(),dangerLevel);

}


//Since fighters don't normally malfunction we have to simulate it
public void checkMalfChance(ShipAPI ship)
{
float cr = ship.getCurrentCR();

if(cr < .20f && cr >= 0f )
{
if(cr == 0f)
{
ship.setDefenseDisabled(true);
ship.setShipSystemDisabled(true);
}

else
{
ship.setDefenseDisabled(false);
ship.setShipSystemDisabled(false);
}

ship.getMutableStats().getEngineMalfunctionChance().modifyFlat(ship.getId(), 0.03f*(1f + (1f-cr) ) );
ship.getMutableStats().getWeaponMalfunctionChance().modifyFlat(ship.getId(), 0.03f*(1f + (1f-cr) ) );
}

else
{
ship.getMutableStats().getEngineMalfunctionChance().unmodify();
ship.getMutableStats().getWeaponMalfunctionChance().unmodify();
}
}

public void drawHud(ShipAPI ship)
{
MagicUI.drawHUDStatusBar(
ship,
(ship.getHitpoints()/ship.getMaxHitpoints()),
null,
null,
1,
"HULL",
">",
true
);

MagicUI.drawHUDStatusBar(
ship, (ship.getCurrFlux()/ship.getMaxFlux()),
null,
null,
(ship.getHardFluxLevel()),
"FLUX",
">",
false
);

MagicUI.drawInterfaceStatusBar(
ship, ship.getCurrentCR(),
getColorCondition(ship),
getColorCondition(ship),
ship.getCurrentCR(),
"CR",
(int)(ship.getCurrentCR()*100)
);
}

public void combatAlert(ShipAPI ship, ShipAPI servicee,String context)
{
switch(context) {
case "overload":
//if(
ship.getFluxTracker().showOverloadFloatyIfNeeded("Overloaded!", TEXT_COLOR, 4f, true);

break;
}
textInterval = new IntervalUtil(100f,100f);
}

public Color getColorCondition(ShipAPI ship)
{
return Global.getSettings().getColor("textFriendColor");
}

public String getDescriptionParam(int index, HullSize hullSize)
{

if (index == 0) return "" + "activating autopilot";

return null;
}

private final Color HL=Global.getSettings().getColor("hColor");
@Override
    public void addPostDescriptionSection(TooltipMakerAPI tooltip, ShipAPI.HullSize hullSize, ShipAPI ship, float width, boolean isForModSpec)
{
float pad = 10f;
float padS = 2f;
tooltip.addSectionHeading("Details", Alignment.MID, 10); 
tooltip.addPara("%s " + "Can %s.", pad, Misc.getHighlightColor(), "-", "maneuver over asteroids and other vessels, unless flamed out");
tooltip.addPara("%s " + "Most weapons %s.", padS, Misc.getHighlightColor(), "-", "fire over allied ships");
tooltip.addPara("%s " + "No %s.", padS, Misc.getHighlightColor(), "-", "zero-flux speed bonus");
tooltip.addPara("%s " + "Combat Readiness decreases %s faster.", padS, Misc.getHighlightColor(), "-", (int) DEGRADE_INCREASE_PERCENT + "%");
tooltip.addPara("%s " + "Docking replenishes %s CR and %s armor/hull/ammo.", padS, Misc.getHighlightColor(), "-",(int)CR + "%",(int)Repair+"%");
tooltip.addPara("%s " + "Suffers additional damage from captains with the %s skill.", padS, Misc.getHighlightColor(), "-","Point Defense");
tooltip.addPara("%s " + "Incapable of %s.", padS, Misc.getHighlightColor(), "-","capturing objectives");

        //title
        tooltip.addSectionHeading("Refit Time Modifiers", Alignment.MID, 10);       
       
        if(ship!=null && ship.getVariant()!=null)
{
float adjustedRate  = 0f;
String weaponName = "";
getRefitRate(ship);
if(Global.getCombatEngine().getCustomData().get("armaa_strikecraftMissileMalus"+ship.getId()) instanceof Float)
{
adjustedRate = (float)Global.getCombatEngine().getCustomData().get("armaa_strikecraftMissileMalus"+ship.getId());
weaponName = String.valueOf(Global.getCombatEngine().getCustomData().get("armaa_strikecraftWepName"+ship.getId()));
}

            if( adjustedRate == 0f){
                tooltip.addPara(
                        "No refit penalty."
                        ,10
                        ,HL
                );

            } else {
                //effect applied
float value = adjustedRate*100.0f;
                String depletion = String.valueOf((int)value)+" percent";
                String weapon = weaponName;
                tooltip.addPara(
                        "Refit time is increased by "
                        + depletion
                        + " due to the installation of: "
                        + weapon
                        ,10
                        ,HL
                        ,depletion
                        ,weapon
                );

            }
        }
       
    }

private void getRefitRate(ShipAPI target)
{
float adjustedRate  = 0f;
String wepName = "";
List<WeaponAPI> weapons = target.getAllWeapons();
for(WeaponAPI w : weapons)
{
if(w.getType() == WeaponType.MISSILE)
{
float damage = w.getDerivedStats().getDamagePerShot();
if(damage > MISSILE_DAMAGE_THRESHOLD)
{
float penalty = damage/MISSILE_DAMAGE_THRESHOLD;

if(penalty < 1)
continue;

penalty = penalty/10f;
float newRate = (float)Math.min(.35f,penalty);
if(newRate > adjustedRate)
{
adjustedRate = newRate;
wepName = w.getDisplayName();
}
Global.getCombatEngine().getCustomData().put("armaa_strikecraftMissileMalus"+target.getId(),adjustedRate);
Global.getCombatEngine().getCustomData().put("armaa_strikecraftWepName"+target.getId(),wepName);
}
}
}
}

//Check if any of our weapons are out of ammo.
    private boolean needsReload(ShipAPI target)
    {
boolean check = false;
getRefitRate(target);
List<WeaponAPI> weapons = target.getAllWeapons();
for(WeaponAPI w : weapons)
{
//So long as we have at least one weapon with some ammo left, don't return yet
if(w.getId().equals("armaa_leynosBoostKnuckle") && Global.getCombatEngine().getPlayerShip() != target)
{
return false;
}

//code to make sure AI strikecraft don't return if they fire their landing beacon
if(w.getId().equals("armaa_landingBeacon"))
{
if(w.getAmmo() < 1 && target == Global.getCombatEngine().getPlayerShip())
{
check = true;
return check;
}

else continue;
}

if(w.usesAmmo() && (w.getAmmo() >= 1 && w.getAmmoPerSecond() == 0) && !(w.getSlot().isDecorative() && !w.getId().equals("armaa_landingBeacon") ))
{
check = false;

}

else if(w.usesAmmo() && w.getAmmo() < 1 && !(w.getSlot().isDecorative()))
{
if(w.getAmmoPerSecond() == 0)
{
check = true;
}
}

}
return check;
    }

//Check if there are any ships that we can land at
private boolean canRefit(ShipAPI ship)
{
boolean canRefit = false;

for (ShipAPI carrier : CombatUtils.getShipsWithinRange(ship.getLocation(), 10000.0F))
{
if(carrier.getOwner() != ship.getOwner() || carrier.isFighter() || carrier.isFrigate())
continue;
if(carrier.isHulk())
continue;
if(carrier.getNumFighterBays() > 0)
{
canRefit = true;
}
}

return canRefit;
}

private ShipAPI getNearestCarrier(ShipAPI ship)
{
ShipAPI potCarrier = null;
float distance = 99999f;
for (ShipAPI carrier : CombatUtils.getShipsWithinRange(ship.getLocation(), 10000.0F))
{
if(carrier.getOwner() != ship.getOwner() || carrier.isFighter() || carrier.isFrigate() || carrier == ship)
continue;
if(carrier.isHulk())
continue;
if(carrier.getNumFighterBays() > 0)
{
if(MathUtils.getDistance(ship, carrier) < distance)
{
distance = MathUtils.getDistance(ship, carrier);
potCarrier = carrier;
}
}
}

return potCarrier;
}

       
private void checkWaypoint(ShipAPI ship)
{
boolean ally = false;
if(ship.isAlly())
ally = true;
if(ship.isRetreating())
return;
CombatFleetManagerAPI cfm = Global.getCombatEngine().getFleetManager(ship.getOwner());
CombatTaskManagerAPI ctm = cfm.getTaskManager(ally);

if(Global.getCombatEngine().getCustomData().get("armaa_strikecraft_hasWaypoint"+ship.getId()) instanceof Boolean)
{
if((Boolean)Global.getCombatEngine().getCustomData().get("armaa_strikecraft_hasWaypoint"+ship.getId()) == true)
{
if(Global.getCombatEngine().getCustomData().get("armaa_strikecraft_refit_timer"+ship.getId()) instanceof Float)
{
float f = (Float)Global.getCombatEngine().getCustomData().get("armaa_strikecraft_refit_timer"+ship.getId());
Global.getCombatEngine().getCustomData().put("armaa_strikecraft_refit_timer"+ship.getId(),f+0.1f);

if(f  > 200f)
{
ctm.orderSearchAndDestroy(cfm.getDeployedFleetMember(ship), false);
Global.getCombatEngine().getCustomData().put("armaa_strikecraft_refit_timer"+ship.getId(),0f);
Global.getCombatEngine().getCustomData().put("armaa_strikecraft_hasWaypoint"+ship.getId(),false);
}
}

else
Global.getCombatEngine().getCustomData().put("armaa_strikecraft_refit_timer"+ship.getId(),0f);
}
}
}

private void retreatToRefit(ShipAPI ship)
{
boolean ally = false;
CombatFleetManagerAPI cfm = Global.getCombatEngine().getFleetManager(ship.getOwner());

if(ship.isRetreating())
return;

if(Global.getCombatEngine().getCustomData().get("armaa_strikecraft_hasWaypoint"+ship.getId()) instanceof Boolean)
{
if((Boolean)Global.getCombatEngine().getCustomData().get("armaa_strikecraft_hasWaypoint"+ship.getId()) == true)
{
return;
}
}

if(ship.isAlly())
ally = true;

ShipAPI carrier = getNearestCarrier(ship);

CombatTaskManagerAPI ctm = cfm.getTaskManager(ally);

DeployedFleetMemberAPI dfm = cfm.getDeployedFleetMember(ship);
if(Global.getCombatEngine().isEntityInPlay(carrier) == false)
return;

//if we have an assignment to escort something
if(ctm.getAssignmentFor(ship) != null && ctm.getAssignmentFor(ship).getType() == CombatAssignmentType.RALLY_TASK_FORCE)
{
//do nothing
return;
}
/*
if(ctm.getAssignmentFor(ship) != null)
{
log.debug(ctm.getAssignmentFor(ship).getType());
log.info(ctm.getAssignmentFor(ship).getType());
log.info(ship.getShipAI().getClass().getName());
}
*/
ship.setHullSize(HullSize.FRIGATE);
ship.resetDefaultAI();
if(ship.getHullSize() == HullSize.FRIGATE)
{
CombatFleetManagerAPI.AssignmentInfo assign = ctm.createAssignment(CombatAssignmentType.RALLY_TASK_FORCE,cfm.createWaypoint(carrier.getLocation(),ally),false);
ctm.giveAssignment(dfm,assign,false);
Global.getCombatEngine().getCustomData().put("armaa_strikecraft_hasWaypoint"+ship.getId(),true);
}
}

    @Override
    public void advanceInCombat(ShipAPI ship, float amount)
    {

if(Global.getCombatEngine() != null)
{
if(!(Global.getCombatEngine().getCustomData().get("armaa_strikecraftLanded"+ship.getId()) instanceof Boolean))
Global.getCombatEngine().getCustomData().put("armaa_strikecraftLanded"+ship.getId(),false);

if(Global.getCombatEngine().getCustomData().get("armaa_strikecraftPilot"+ship.getId()) instanceof Float == false)
{
getPilotPersonality(ship);
}

else
HPPercent = (float)Global.getCombatEngine().getCustomData().get("armaa_strikecraftPilot"+ship.getId());
if(Global.getCombatEngine().getCustomData().get("armaa_strikecraft_refit_timer"+ship.getId()) instanceof Float)
{
float f = (Float) Global.getCombatEngine().getCustomData().get("armaa_strikecraft_refit_timer"+ship.getId());
}
}

if(ship.getCollisionClass() == CollisionClass.SHIP)
ship.setCollisionClass(CollisionClass.FIGHTER);

boolean player = ship == Global.getCombatEngine().getPlayerShip();

CombatUIAPI ui = Global.getCombatEngine().getCombatUI();

checkMalfChance(ship);

if(ship.getCurrentCR() <= 0.4f && Global.getCombatEngine().getCombatUI() != null && ship.getOriginalOwner() != -1)
{
if(ship.getOwner() == 0 && !ship.isAlly())
if(!(Global.getCombatEngine().getCustomData().get("armaa_strikecraftCRWarning"+ship.getId()) instanceof Boolean))
{
Global.getCombatEngine().getCombatUI().addMessage(1,Global.getSettings().getColor("textFriendColor"),ship.getName() + "'s ",Color.yellow, "performance is degrading due to combat stresses.");
Global.getSoundPlayer().playUISound("cr_allied_critical", 1f, 1f);
Global.getCombatEngine().getCustomData().put("armaa_strikecraftCRWarning"+ship.getId(),true);
}
}

float CurrentHull = ship.getHitpoints();
float MaxHull = ship.getMaxHitpoints();
float CurrentCR = ship.getCurrentCR();
boolean needsrefit = false;
if (((CurrentHull <= MaxHull * HPPercent) || (CurrentCR < CRmarker) || (needsReload(ship))) && canRefit(ship))
{
if((player && !Global.getCombatEngine().isUIAutopilotOn() && ship.getShipAI() == null))
{
ship.setHullSize(HullSize.FRIGATE);
ship.resetDefaultAI();
}

if (player)
Global.getCombatEngine().maintainStatusForPlayerShip("AceSystem1", "graphics/ui/icons/icon_repair_refit.png","/ - WARNING - /", "REFIT / REARM NEEDED( PRESS " + Global.getSettings().getControlStringForEnumName("C2_TOGGLE_AUTOPILOT") + " )" ,true);

if((!player && canRefit(ship)) || (player && !Global.getCombatEngine().isUIAutopilotOn()))
{
if(!ship.isFinishedLanding())
{
needsrefit  = true;
retreatToRefit(ship);
}

armaa_combat_docking_AI DockingAI = new armaa_combat_docking_AI(ship);
if(ship.controlsLocked() && !ship.getShipAI().needsRefit())
{
if(ship.isFinishedLanding())
{
ship.setAnimatedLaunch();
}
ship.setShipAI(DockingAI);
}
if(!ship.isFinishedLanding() && getNearestCarrier(ship) != null && MathUtils.getDistance(ship, getNearestCarrier(ship)) < 1500f)
{
//Do init -once-
Global.getCombatEngine().getCustomData().put("armaa_strikecraftisLanding"+ship.getId(),true);
ship.setShipAI(DockingAI);
DockingAI.init();
}
else
{
if(Global.getCombatEngine().getCustomData().get("armaa_strikecraftLanded"+ship.getId()) instanceof Boolean)
{
boolean hasLanded = (boolean)Global.getCombatEngine().getCustomData().get("armaa_strikecraftLanded"+ship.getId());
if(player && !hasLanded && ship.isFinishedLanding())
{
Random rand = new Random();
String txt =  landingLines_Critical.get(rand.nextInt(landingLines_Critical.size()));
if(ship.getHullLevel() > 0.45f)
txt =  landingLines_Good.get(rand.nextInt(landingLines_Good.size()));
Vector2f texLoc;
if(DockingAI.getCarrier() != null)
texLoc = DockingAI.getCarrier().getLocation();
else
texLoc = ship.getLocation();
if(Math.random() <= 35f)
{
ship.getFluxTracker().showOverloadFloatyIfNeeded(txt, Color.white, 2f, true);
Global.getSoundPlayer().playSound("ui_noise_static", 1f, 1f, texLoc,new Vector2f());
}
Global.getCombatEngine().getCustomData().put("armaa_strikecraftLanded"+ship.getId(),true);
}
}

Global.getCombatEngine().getCustomData().put("armaa_strikecraftRefitTime"+ship.getId(),DockingAI.getCarrierRefitRate());
}
}

}

if(ship.getFluxTracker().isOverloaded())
{
float shipRadius = armaa_utils.effectiveRadius(ship);
if(Math.random() < 0.05f)
for (int i = 0; i < 1; i++)
{

Vector2f targetPoint = MathUtils.getRandomPointInCircle(ship.getLocation(), (shipRadius * 0.75f + 15f) * 1f);
Vector2f anchorPoint = MathUtils.getRandomPointInCircle(ship.getLocation(), shipRadius);
AnchoredEntity anchor = new AnchoredEntity(ship, anchorPoint);
float thickness = (float) Math.sqrt(((shipRadius * 0.025f + 5f) * 1f) * MathUtils.getRandomNumberInRange(0.75f, 1.25f)) * 3f;
Color coreColor = new Color(TEXT_COLOR.getRed(), TEXT_COLOR.getGreen(), TEXT_COLOR.getBlue(), 255);
EmpArcEntityAPI arc = Global.getCombatEngine().spawnEmpArcPierceShields(ship, targetPoint, anchor, anchor, DamageType.ENERGY,
0f, 0f, shipRadius, null, thickness, new Color(155,50,250,150), coreColor);
}
if(textInterval.intervalElapsed())
{
Global.getSoundPlayer().playSound("emp_loop", 1f, .8f, ship.getLocation(), new Vector2f());
combatAlert(ship,null,"overload");
}

else textInterval.advance(0.65f);
}

if(ui != null)
{
if(canRetreat(ship) || ship.getTravelDrive().isOn() || needsrefit)
{
ship.setHullSize(HullSize.FRIGATE);
}

else
{
drawHud(ship);
ship.setHullSize(HullSize.FIGHTER);
}
}

if(ship.getOriginalOwner() == -1 || Global.getCombatEngine().isCombatOver() || Global.getCombatEngine().isPaused())
ship.setHullSize(HullSize.FRIGATE);

checkWaypoint(ship);
    }


public static class StrikeCraftDamageListener implements DamageListener {
ShipAPI ship;

public StrikeCraftDamageListener(ShipAPI ship)
{
this.ship = ship;
}

public void reportDamageApplied(Object source, CombatEntityAPI target, ApplyDamageResultAPI result)
{
float totalDamage = result.getDamageToHull() + result.getTotalDamageToArmor();
if(source instanceof WeaponAPI)
{
WeaponAPI wep = (WeaponAPI)source;
ShipAPI ship = (ShipAPI) target;

//We dont have the armor to tank beams like larger ships, back off if we start to take too much hull/armor damage
if(wep.isBeam() && totalDamage > 20)
{
ShipwideAIFlags flags = ship.getAIFlags();
flags.setFlag(ShipwideAIFlags.AIFlags.BACK_OFF,1f);
flags.setFlag(ShipwideAIFlags.AIFlags.DO_NOT_PURSUE,1f);
}
}

//Damage floaties since they aren't created for fighter hullsize
if(ship.getHullSize() == HullSize.FIGHTER && Global.getCombatEngine().getPlayerShip() == ship)
{
float shipRadius = armaa_utils.effectiveRadius(ship);
Vector2f anchorPoint = MathUtils.getRandomPointInCircle(ship.getLocation(), shipRadius);
Global.getCombatEngine().addFloatingDamageText(anchorPoint, result.getTotalDamageToArmor(), Color.yellow, ship, (CombatEntityAPI)source);
anchorPoint.setY(anchorPoint.getY()-20);
Global.getCombatEngine().addFloatingDamageText(anchorPoint, result.getDamageToHull(), Color.red, ship, (CombatEntityAPI)source);
anchorPoint.setY(anchorPoint.getY()-20);
Global.getCombatEngine().addFloatingDamageText(anchorPoint, result.getDamageToShields(), new Color(125,125,200,255), ship, (CombatEntityAPI)source);
}
}
}

//This listener ensures we die properly
public static class StrikeCraftDeathMod implements DamageTakenModifier, AdvanceableListener
{
protected ShipAPI ship;
public StrikeCraftDeathMod(ShipAPI ship) {
this.ship = ship;
}

public void advance(float amount)
{

}

public String modifyDamageTaken(Object param,CombatEntityAPI target, DamageAPI damage, Vector2f point, boolean shieldHit)
{

if(!(target instanceof ShipAPI))
return null;

if(shieldHit)
return null;
float multiplier = 1f;
switch(damage.getType())
{
case KINETIC:
multiplier = 0.5f;
break;

case HIGH_EXPLOSIVE:
multiplier = 2f;
break;

case FRAGMENTATION:
multiplier = 0.25f;
}
ShipAPI s = (ShipAPI) target;
float damageVal = damage.getDamage();
float armor = DefenseUtils.getArmorValue(s,point);
if(damageVal*(damageVal/(armor+Math.max(damageVal*0.05,damageVal))) > s.getHitpoints())
s.setHullSize(HullSize.FRIGATE);

String id = "strikecraft_death";
return id;
}
}
}



2.) A baseeveryframecombatplugin that prevents combat from prematurely ending if only ships with the above hullmod remain. also prevents these ships being assigned to capture objectives as well as forcibly resetting AI for any ships that end up using Fighter AI for some reason(as this renders them unresponsive)

Code: armaa_EinhanderHaxPlugin
package data.scripts.plugins;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.mission.FleetSide;
import com.fs.starfarer.api.input.InputEventAPI;
import java.util.ArrayList;
import com.fs.starfarer.api.combat.ShipAPI.HullSize;
import java.util.List;
import com.fs.starfarer.api.combat.CombatFleetManagerAPI.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lazywizard.lazylib.CollisionUtils;
import org.lwjgl.util.vector.Vector2f;
import data.scripts.util.MagicRender;
import java.awt.Color;
import com.fs.starfarer.api.combat.MissileAPI;

//import org.lazywizard.console.commands.ToggleAI;

//this script manages things that otherwise wouldn't be possible via hullmod; primarily ensuring fake fighters being only ship left on field doesnt end combat
//and ensuring its always selectable in UI
public class armaa_EinhanderHaxPlugin extends BaseEveryFrameCombatPlugin
{
    protected CombatEngineAPI engine;
protected List<ShipAPI> cataphrachtii = new ArrayList<ShipAPI>();
protected List<ShipAPI> toRemove = new ArrayList<ShipAPI>();
protected CombatTaskManagerAPI ctm;
protected CombatFleetManagerAPI cfm;
protected CombatUIAPI ui;
private String origPersonality = "steady";
boolean canEnd = false;

    @Override
    public void advance(float amount, List<InputEventAPI> events)
    {
if(engine == null)
return;

cfm = engine.getFleetManager(FleetSide.PLAYER);
ctm = cfm.getTaskManager(false);
ui = engine.getCombatUI();

for (MissileAPI missile : engine.getMissiles())
        {
if(missile.getWeapon() != null)
{
if(missile.getWeapon().getId().equals("armaa_altagrave_rightArm"))
{
if(missile.isFizzling() || missile.isFading()){
if(MagicRender.screenCheck(0.25f, missile.getLocation())){
engine.addSmoothParticle(missile.getLocation(), new Vector2f(), 50, 0.5f, 0.25f, Color.red);
engine.addHitParticle(missile.getLocation(), new Vector2f(), 50, 1f, 0.1f, new Color(250,192,92,255));
}
engine.removeEntity(missile);
return;
}
}
else if(missile.getWeapon().getId().equals("armaa_sprigganTorso"))
{
if(missile.isFizzling() || missile.isFading()){
if(MagicRender.screenCheck(0.25f, missile.getLocation())){
engine.addSmoothParticle(missile.getLocation(), new Vector2f(), 50, 0.5f, 0.15f, Color.blue);
engine.addHitParticle(missile.getLocation(), new Vector2f(), 50, 1f, 0.25f, new Color(250,192,250,255));
}
engine.removeEntity(missile);
return;
}
}
}

else continue;
}

        for (ShipAPI ship : engine.getShips())
        {
            if(ship.getHullSpec().getBuiltInMods().contains("strikeCraft"))
{
if(!cataphrachtii.contains(ship))
{
cataphrachtii.add(ship);
}
}

else continue;
}

if(cataphrachtii.isEmpty() || engine.isEnemyInFullRetreat() || engine.isCombatOver() || engine.getFleetManager(0).getTaskManager(false).isInFullRetreat())
{
engine.setDoNotEndCombat(false);
canEnd = true;
//Global.getCombatEngine().maintainStatusForPlayerShip("debug", "graphics/ui/icons/icon_repair_refit.png","Retreat OK" ,"" ,true);
}

if(cataphrachtii.isEmpty())
{
return;
}

if(!cataphrachtii.isEmpty())
{
if(!engine.isEnemyInFullRetreat() || !engine.getFleetManager(0).getTaskManager(false).isInFullRetreat())
{
if(!canEnd)
engine.setDoNotEndCombat(true);
}
//engine.setDoNotEndCombat(true);
for(ShipAPI mech : cataphrachtii)
{
if(mech.getShipAI() instanceof com.fs.starfarer.combat.ai.FighterAI || mech.getShipAI() == null && mech != engine.getPlayerShip())
{
mech.setHullSize(HullSize.FRIGATE);
mech.resetDefaultAI();
}

if(ui != null)
{
if((ui.isShowingCommandUI()))
{
if(mech.getOwner() == 0)
{
CombatFleetManagerAPI cfm = Global.getCombatEngine().getFleetManager(mech.getOwner());
CombatTaskManagerAPI ctm = cfm.getTaskManager(false);
if(ctm.getAssignmentFor(mech) != null )
if(ctm.getAssignmentFor(mech).getType() == CombatAssignmentType.CAPTURE ||
ctm.getAssignmentFor(mech).getType() == CombatAssignmentType.ASSAULT ||
ctm.getAssignmentFor(mech).getType() == CombatAssignmentType.CONTROL)
{
if(mech.getHullSize() == HullSize.FRIGATE)
{
DeployedFleetMemberAPI dfm = cfm.getDeployedFleetMember(mech);
Global.getCombatEngine().getCombatUI().addMessage(1,Global.getSettings().getColor("textFriendColor"),mech.getName(),Color.red, " is incapable of capturing objectives due to being a strikecraft.");
Global.getSoundPlayer().playUISound("cr_allied_critical", 0.5f, 1f);
ctm.orderSearchAndDestroy(dfm,false);
}
}


}
if(mech.getHullSize() != HullSize.FRIGATE)
mech.setHullSize(HullSize.FRIGATE);
}
}

if(mech == null || mech.isRetreating())
{
toRemove.add(mech);
}
// should no longer be needed, but leaving it here just in case
if(mech.getHitpoints() <= 0f || mech.isHulk() || mech.getOwner() == 100)
{
toRemove.add(mech);
}
}
}
if(!toRemove.isEmpty())
cataphrachtii.removeAll(toRemove);
}

    @Override
    public void init(CombatEngineAPI engine)
    {
        this.engine = engine;
    }
}
3.) An AI script for landing on carriers and simulating repairs/reload(I use a modified version of Harupea's Combat Docking Module)

This approach covers most oddities that can arise for consistent behavior, the exception being when the ship is in their retreat area(to prevent crashes when retreating) and when the command UI is open(to allow ordering the ship), where they are temporarily switched back to the frigate HullSize.

Quirks of Fighter Hullsize
- Frigates and up generally won't prioritize these ships unless there is nothing else around, or they generate enough "heat". Frigates and Destroyers are more likely to do so, while larger ships seem to go down this route when panicking, otherwise leaving things to their autofire weapons.
- Capitals will never try to face these ships, but will happily blast them should they enter the firing arc of their manual-firing weapons and autofiring weapons will attack as normally.
- Interceptors will specifically target as if a normal fighter
- Damage received will be modified by any fighter PD bonuses the damage happens to have
- Ship is treated as a Frigate for the purpose of skill bonuses
- AI will not use any strike weapon to attack these ships, unless they panic
« Last Edit: December 13, 2021, 11:50:03 PM by shoi »
Logged

shoi

  • Admiral
  • *****
  • Posts: 657
    • View Profile
Re: How to make playable fighters
« Reply #1 on: November 17, 2021, 04:37:54 AM »

reserved just in case
Logged

6chad.noirlee9

  • Captain
  • ****
  • Posts: 368
    • View Profile
Re: How to make playable fighters
« Reply #2 on: November 18, 2021, 06:22:52 AM »

Cool of you to do a tutorial
Logged
edit: edit: maybe were just falling with style LOL.  make a bubble, make the space in front of it smaller and just fall forward

Mephansteras

  • Commander
  • ***
  • Posts: 102
    • View Profile
Re: How to make playable fighters
« Reply #3 on: November 18, 2021, 09:38:41 AM »

Neat, good to know!
Logged