Something like.... The ability to repair the hull of a ship in battle.No. hull regeneration was the biggest enabler of soloing fleets. You trade shots with the enemy, run away, let hull regenerate, repeat. It is was single most powerful perk before 0.8.
No. hull regeneration was the biggest enabler of soloing fleets. You trade shots with the enemy, run away, let hull regenerate, repeat. It is was single most powerful perk before 0.8.I don't think this is an issue at all any more, given that CR exists for everything and the AI is fond of dragging out engagements as long as possible. Not to mention soloing is dead now, if you try it outside the simulator you're gonna have a bad time. And balancing for edge cases is not really a good idea anyway.
Today, using this hullmod, along with many others, hurts, especially if you do not have Loadout Design 3 (and it still hurts even when you have it).You'd be giving up OP space (which there is already, as noted, a dearth of), essentially trading it for survivability. Which is something I greatly miss from 0.7 where I could have damage control officers on my vital ships, greatly reducing my reluctance to deploy them.
Now, maybe have the hullmod repair some damage after combat for free like Damage Control 2 and Field Repairs 2. That would be nice.Nice, yes. I suppose it would be kind of neat to have access to that ability at the cost of OP instead of skill points for the sake of continuity with the rest of the abilities which have this. It would be nicer still if there were some means of performing in-combat repairs again. Even if it has some conditional thing like the ship must be at 0 flux, or the ARU generates flux when under heavy use.
Currently i find the hullmod very useful when setting up ships to fight an enemy that is very EMP heavy. certain modded factions like DME or the Templars, have a lot of EMP built into their stuff. ships like the onslaught which would prefer its shields to be down, or cant protect its rear benefit from this hullmod a lot, too.
The regeneration is kind of a weird thing, with some of the armor damage reduction applying to hull now it could become far stronger than it already was.
The issue with armor regen is that there is no way to remove damage decals. Or so I've heard at least.
package data.hullmods;
import com.fs.starfarer.api.combat.BaseHullMod;
import com.fs.starfarer.api.combat.ArmorGridAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipAPI.HullSize;
import com.fs.starfarer.api.fleet.FleetMemberAPI;
import com.fs.starfarer.api.fleet.RepairTrackerAPI;
import java.util.HashMap;
import java.util.Map;
class Constants {
//Up to how much HP in total will be repaired, in multiples of ship's total armor HP
//Default: 1
public static final float TOTAL_REPAIR_FACTOR = 1f;
//How much CR will be lost to repair the maximum amount of armor, in fractions of 100%
//Default: 0.1 (10%)
public static final float TOTAL_CR_LOSS = 0.1f;
//How much of damage taken will be repaired, in fractions of 100%
//Default: 0.8 (80%)
public static final float BASE_REPAIR_EFFICIENCY = 0.8f;
//How long after taking damage until repair starts, in seconds
//Default: 10
public static final float REPAIR_TIMEOUT = 10f;
//Base repair rate, in HP per armor cell per second
//Default: 0.2
public static final float BASE_REPAIR_RATE = 0.2f;
//Scaling repair rate, in percent per second
//Default: 0.005 (0.5% per second)
public static final float REPAIR_RATE = 0.01f;
//How the repair efficiency drops off over time
//1 is linear, 2 is default, higher values start dropping off later but drop more sharply
//See curves.png in root directory of the mod for reference
public static final float EFFICIENCY_CURVE_POWER = 2f;
}
class ShipArmorData {
public ArmorGridAPI armorGrid;
public int armorCellsX, armorCellsY;
public float[][] sinceLastDamage;
public float[][] expectedRepair;
public float[][] armorCells;
public float repairEfficiency;
public float totalRepaired;
public float totalRepairedLast;
public float totalCRLost;
public float totalRepairCapacity;
public float maxArmor;
public boolean initialArmorLoaded;
public boolean CRSynched = false;
public RepairTrackerAPI RepairTracker;
public ShipArmorData(ShipAPI ship)
{
repairEfficiency = Constants.BASE_REPAIR_EFFICIENCY;
totalRepaired = 0f;
totalRepairedLast = 0f;
totalCRLost = 0f;
armorGrid = ship.getArmorGrid();
maxArmor = armorGrid.getMaxArmorInCell();
float[][] armorCellsT = armorGrid.getGrid();
armorCellsX = armorCellsT.length;
armorCellsY = armorCellsT[0].length;
totalRepairCapacity = armorGrid.getArmorRating() * Constants.TOTAL_REPAIR_FACTOR * armorCellsX * armorCellsY / 15f;
sinceLastDamage = new float[armorCellsX][armorCellsY];
armorCells = new float[armorCellsX][armorCellsY];
expectedRepair = new float[armorCellsX][armorCellsY];
initialArmorLoaded = false;
}
}
public class ARHM_ArmorRepairHullmod extends BaseHullMod {
Map<String, ShipArmorData> Ships = new HashMap<String, ShipArmorData>();
public void advanceInCampaign(FleetMemberAPI member, float amount)
{
ShipArmorData armorData = Ships.get(member.getId());
if(armorData != null)
{
if(!armorData.CRSynched && armorData.totalCRLost > 0)
{
armorData.RepairTracker = member.getRepairTracker();
armorData.RepairTracker.applyCREvent(-armorData.totalCRLost, "Armor Repair System use");
armorData.CRSynched = true;
}
}
}
public void applyEffectsAfterShipCreation(ShipAPI ship, String id)
{
String Id = ship.getFleetMemberId();
if(Id != null)
{
Ships.put(ship.getFleetMemberId(), new ShipArmorData(ship));
}
else
{
Ships.put("" + ship.hashCode(), new ShipArmorData(ship));
}
}
public void advanceInCombat(ShipAPI ship, float amount) {
String Id = ship.getFleetMemberId();
ShipArmorData armorData;
if(Id != null)
{
armorData = Ships.get(Id);
}
else
{
armorData = Ships.get("" + ship.hashCode());
}
armorData.CRSynched = false;
if(!armorData.initialArmorLoaded)
{
for(int i = 0; i < armorData.armorCellsX; i++)
{
for(int ii = 0; ii < armorData.armorCellsY; ii++)
{
armorData.armorCells[i][ii] = armorData.armorGrid.getArmorValue(i, ii);
}
}
armorData.initialArmorLoaded = true;
}
for(int i = 0; i < armorData.armorCellsX; i++)
{
for(int ii = 0; ii < armorData.armorCellsY; ii++)
{
float armorValue = armorData.armorGrid.getArmorValue(i, ii);
if(armorValue < armorData.armorCells[i][ii])
{
armorData.sinceLastDamage[i][ii] = 0f;
armorData.expectedRepair[i][ii] += (armorData.armorCells[i][ii] - armorValue);
}
else
{
armorData.sinceLastDamage[i][ii] += amount;
}
armorData.armorCells[i][ii] = armorValue;
}
}
for(int i = 0; i < armorData.armorCellsX; i++)
{
for(int ii = 0; ii < armorData.armorCellsY; ii++)
{
float armorValue = armorData.armorGrid.getArmorValue(i, ii);
if(armorData.sinceLastDamage[i][ii] > Constants.REPAIR_TIMEOUT && armorValue < armorData.maxArmor && armorData.expectedRepair[i][ii] > 0)
{
float maxRepairFromExpected = armorData.expectedRepair[i][ii] * armorData.repairEfficiency;
float maxRepairFromRate = amount * (Constants.BASE_REPAIR_RATE + (armorData.maxArmor * Constants.REPAIR_RATE));
float repairAmount = Math.min(maxRepairFromExpected, maxRepairFromRate);
repairAmount = Math.min(armorData.maxArmor - armorValue, repairAmount);
armorData.armorGrid.setArmorValue(i, ii, armorValue + repairAmount);
if(maxRepairFromExpected < maxRepairFromRate)
{
armorData.expectedRepair[i][ii] = 0;
}
else
{
armorData.expectedRepair[i][ii] -= maxRepairFromRate / armorData.repairEfficiency;
}
armorData.totalRepaired += repairAmount;
}
}
}
float repairDelta = armorData.totalRepaired - armorData.totalRepairedLast;
float CRLoss = (repairDelta / armorData.totalRepairCapacity) * Constants.TOTAL_CR_LOSS;
ship.setCurrentCR(ship.getCurrentCR() - CRLoss);
armorData.totalCRLost += CRLoss;
armorData.totalRepairedLast = armorData.totalRepaired;
armorData.repairEfficiency = Math.max(Constants.BASE_REPAIR_EFFICIENCY * (1 - (float)Math.pow((armorData.totalRepaired / armorData.totalRepairCapacity), Constants.EFFICIENCY_CURVE_POWER)), 0);
}
public String getDescriptionParam(int index, HullSize hullSize)
{
if (index == 0) {return "" + (int) (Constants.REPAIR_RATE * 100) + "%";}
if (index == 1) {return "" + (int) (Constants.REPAIR_TIMEOUT);}
if (index == 2) {return "" + (int) (Constants.TOTAL_REPAIR_FACTOR);}
if (index == 3) {return "" + (int) (Constants.BASE_REPAIR_EFFICIENCY * 100) + "%";}
if (index == 4) {return "" + (int) (Constants.TOTAL_CR_LOSS * 100 ) + "%";}
return null;
}
public boolean isApplicableToShip(ShipAPI ship) {
return ship != null;
}
I wrote something similar a while ago if anyone wants to mess with the idea. Starts out repairing 80% of armor damage taken, gets worse as it repairs more eventually decaying towards zero, further hits while armor is repairing interfere with the process (so it works best on infrequent, heavy hits). Also drains some CR.
It's 0.65 code so I don't know if it'll work. Also probably bad code because I don't know Java.SpoilerCodepackage data.hullmods;
import com.fs.starfarer.api.combat.BaseHullMod;
import com.fs.starfarer.api.combat.ArmorGridAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipAPI.HullSize;
import com.fs.starfarer.api.fleet.FleetMemberAPI;
import com.fs.starfarer.api.fleet.RepairTrackerAPI;
import java.util.HashMap;
import java.util.Map;
class Constants {
//Up to how much HP in total will be repaired, in multiples of ship's total armor HP
//Default: 1
public static final float TOTAL_REPAIR_FACTOR = 1f;
//How much CR will be lost to repair the maximum amount of armor, in fractions of 100%
//Default: 0.1 (10%)
public static final float TOTAL_CR_LOSS = 0.1f;
//How much of damage taken will be repaired, in fractions of 100%
//Default: 0.8 (80%)
public static final float BASE_REPAIR_EFFICIENCY = 0.8f;
//How long after taking damage until repair starts, in seconds
//Default: 10
public static final float REPAIR_TIMEOUT = 10f;
//Base repair rate, in HP per armor cell per second
//Default: 0.2
public static final float BASE_REPAIR_RATE = 0.2f;
//Scaling repair rate, in percent per second
//Default: 0.005 (0.5% per second)
public static final float REPAIR_RATE = 0.01f;
//How the repair efficiency drops off over time
//1 is linear, 2 is default, higher values start dropping off later but drop more sharply
//See curves.png in root directory of the mod for reference
public static final float EFFICIENCY_CURVE_POWER = 2f;
}
class ShipArmorData {
public ArmorGridAPI armorGrid;
public int armorCellsX, armorCellsY;
public float[][] sinceLastDamage;
public float[][] expectedRepair;
public float[][] armorCells;
public float repairEfficiency;
public float totalRepaired;
public float totalRepairedLast;
public float totalCRLost;
public float totalRepairCapacity;
public float maxArmor;
public boolean initialArmorLoaded;
public boolean CRSynched = false;
public RepairTrackerAPI RepairTracker;
public ShipArmorData(ShipAPI ship)
{
repairEfficiency = Constants.BASE_REPAIR_EFFICIENCY;
totalRepaired = 0f;
totalRepairedLast = 0f;
totalCRLost = 0f;
armorGrid = ship.getArmorGrid();
maxArmor = armorGrid.getMaxArmorInCell();
float[][] armorCellsT = armorGrid.getGrid();
armorCellsX = armorCellsT.length;
armorCellsY = armorCellsT[0].length;
totalRepairCapacity = armorGrid.getArmorRating() * Constants.TOTAL_REPAIR_FACTOR * armorCellsX * armorCellsY / 15f;
sinceLastDamage = new float[armorCellsX][armorCellsY];
armorCells = new float[armorCellsX][armorCellsY];
expectedRepair = new float[armorCellsX][armorCellsY];
initialArmorLoaded = false;
}
}
public class ARHM_ArmorRepairHullmod extends BaseHullMod {
Map<String, ShipArmorData> Ships = new HashMap<String, ShipArmorData>();
public void advanceInCampaign(FleetMemberAPI member, float amount)
{
ShipArmorData armorData = Ships.get(member.getId());
if(armorData != null)
{
if(!armorData.CRSynched && armorData.totalCRLost > 0)
{
armorData.RepairTracker = member.getRepairTracker();
armorData.RepairTracker.applyCREvent(-armorData.totalCRLost, "Armor Repair System use");
armorData.CRSynched = true;
}
}
}
public void applyEffectsAfterShipCreation(ShipAPI ship, String id)
{
String Id = ship.getFleetMemberId();
if(Id != null)
{
Ships.put(ship.getFleetMemberId(), new ShipArmorData(ship));
}
else
{
Ships.put("" + ship.hashCode(), new ShipArmorData(ship));
}
}
public void advanceInCombat(ShipAPI ship, float amount) {
String Id = ship.getFleetMemberId();
ShipArmorData armorData;
if(Id != null)
{
armorData = Ships.get(Id);
}
else
{
armorData = Ships.get("" + ship.hashCode());
}
armorData.CRSynched = false;
if(!armorData.initialArmorLoaded)
{
for(int i = 0; i < armorData.armorCellsX; i++)
{
for(int ii = 0; ii < armorData.armorCellsY; ii++)
{
armorData.armorCells[i][ii] = armorData.armorGrid.getArmorValue(i, ii);
}
}
armorData.initialArmorLoaded = true;
}
for(int i = 0; i < armorData.armorCellsX; i++)
{
for(int ii = 0; ii < armorData.armorCellsY; ii++)
{
float armorValue = armorData.armorGrid.getArmorValue(i, ii);
if(armorValue < armorData.armorCells[i][ii])
{
armorData.sinceLastDamage[i][ii] = 0f;
armorData.expectedRepair[i][ii] += (armorData.armorCells[i][ii] - armorValue);
}
else
{
armorData.sinceLastDamage[i][ii] += amount;
}
armorData.armorCells[i][ii] = armorValue;
}
}
for(int i = 0; i < armorData.armorCellsX; i++)
{
for(int ii = 0; ii < armorData.armorCellsY; ii++)
{
float armorValue = armorData.armorGrid.getArmorValue(i, ii);
if(armorData.sinceLastDamage[i][ii] > Constants.REPAIR_TIMEOUT && armorValue < armorData.maxArmor && armorData.expectedRepair[i][ii] > 0)
{
float maxRepairFromExpected = armorData.expectedRepair[i][ii] * armorData.repairEfficiency;
float maxRepairFromRate = amount * (Constants.BASE_REPAIR_RATE + (armorData.maxArmor * Constants.REPAIR_RATE));
float repairAmount = Math.min(maxRepairFromExpected, maxRepairFromRate);
repairAmount = Math.min(armorData.maxArmor - armorValue, repairAmount);
armorData.armorGrid.setArmorValue(i, ii, armorValue + repairAmount);
if(maxRepairFromExpected < maxRepairFromRate)
{
armorData.expectedRepair[i][ii] = 0;
}
else
{
armorData.expectedRepair[i][ii] -= maxRepairFromRate / armorData.repairEfficiency;
}
armorData.totalRepaired += repairAmount;
}
}
}
float repairDelta = armorData.totalRepaired - armorData.totalRepairedLast;
float CRLoss = (repairDelta / armorData.totalRepairCapacity) * Constants.TOTAL_CR_LOSS;
ship.setCurrentCR(ship.getCurrentCR() - CRLoss);
armorData.totalCRLost += CRLoss;
armorData.totalRepairedLast = armorData.totalRepaired;
armorData.repairEfficiency = Math.max(Constants.BASE_REPAIR_EFFICIENCY * (1 - (float)Math.pow((armorData.totalRepaired / armorData.totalRepairCapacity), Constants.EFFICIENCY_CURVE_POWER)), 0);
}
public String getDescriptionParam(int index, HullSize hullSize)
{
if (index == 0) {return "" + (int) (Constants.REPAIR_RATE * 100) + "%";}
if (index == 1) {return "" + (int) (Constants.REPAIR_TIMEOUT);}
if (index == 2) {return "" + (int) (Constants.TOTAL_REPAIR_FACTOR);}
if (index == 3) {return "" + (int) (Constants.BASE_REPAIR_EFFICIENCY * 100) + "%";}
if (index == 4) {return "" + (int) (Constants.TOTAL_CR_LOSS * 100 ) + "%";}
return null;
}
public boolean isApplicableToShip(ShipAPI ship) {
return ship != null;
}[close]