Files
starruler-linux/scripts/server/objects/Ship.as
T
2018-07-17 14:15:37 +02:00

1631 lines
39 KiB
ActionScript

import resources;
import util.target_search;
import regions.regions;
import saving;
import attributes;
from generic_effects import RegionChangeable, LeaderChangeable;
from influence_global import giveRandomReward, DiplomacyEdictType;
from designs import getDesignMesh;
from statuses import getStatusID;
MeshDesc shipMesh;
//Amount of points per unit of size of a ship
const double SHIP_SIZE_POINTS = 0.0;
//Rate of repair in combat
const double COMBAT_REPAIR_MOD = 1.0 / 4.0;
//Amount of effectiveness gained from energy = size
const float ENERGY_EFFECTIVENESS = 1.25f;
//Threshold at which lack of supply starts causing efficiency loss
const float SUPPLY_THRESHOLD = 0.25f;
//Lowest efficiency supply can make a fleet go down to
const float SUPPLY_EFFICIENCY = 0.2f;
//Supply rate in trade border systems
const float SUPPLY_TRADE_RATE = 0.26f;
//Supply rate in unowned systems
const float SUPPLY_UNOWNED_RATE = 0.12f;
//Rate at which supression recovers
const float SUPPRESSION_REGEN = 0.05f;
//Chance of extinguishing fire per second
const float EXTINGUISH_CHANCE = 0.2f;
//Fire damage-per-second per size
const float FIRE_DPS = 0.25;
tidy class ShipScript {
Object@ lastHitBy;
Empire@ killCredit;
uint bpStatusID = 0;
bool barDelta = false, onFire = false, needRepair = true, shieldDelta = false;
int prevSupply = 0;
int currentMaintenance = 0;
float mass = 0.f;
float massBonus = 0.f;
float currentRepair = 0.f;
float currentRepairCost = 0.f;
float shipEffectiveness = 0.f;
float bonusEffectiveness = 0.f;
float suppression = 0.f;
float wreckage = 0.f;
float shieldRegen = 0.f;
Object@ cachedLeader;
const Design@ retrofitTo;
const Design@ queuedRetrofit;
float timer = 0.f, bpTimer = 0.f;
float supplyBonus = 0.f;
int disableRegionVision = 0;
int holdFire = 0;
float movementAccel = 0;
float bonusShield = 0;
float supplyConsumeFactor = 1.f;
ShipScript() {
}
void save(Ship& ship, SaveFile& file) {
saveObjectStates(ship, file);
file << cast<Savable>(ship.Mover);
if(ship.hasLeaderAI) {
file << true;
file << cast<Savable>(ship.LeaderAI);
}
else {
file << false;
}
if(ship.hasSupportAI) {
file << true;
file << cast<Savable>(ship.SupportAI);
}
else {
file << false;
}
if(ship.hasAbilities) {
file << true;
file << cast<Savable>(ship.Abilities);
}
else {
file << false;
}
if(ship.hasStatuses) {
file << true;
file << cast<Savable>(ship.Statuses);
}
else {
file << false;
}
if(ship.hasCargo) {
file << true;
file << cast<Savable>(ship.Cargo);
}
else {
file << false;
}
if(ship.hasConstruction) {
file << true;
file << cast<Savable>(ship.Construction);
}
else {
file << false;
}
if(ship.hasOrbit) {
file << true;
file << cast<Savable>(ship.Orbit);
}
else {
file << false;
}
file << currentMaintenance;
file << ship.isFree;
file << ship.isFTLing;
file << ship.delayFTL;
file << prevSupply;
file << ship.Energy;
file << ship.MaxEnergy;
file << ship.Supply;
file << ship.MaxSupply;
file << ship.formationDest;
ship.blueprint.save(ship, file);
file << ship.Leader;
file << ship.RetrofittingAt;
file << retrofitTo;
file << queuedRetrofit;
file << suppression;
file << wreckage;
file << onFire;
file << supplyBonus;
file << massBonus;
file << ship.Shield;
file << ship.MaxShield;
file << shieldRegen;
file << disableRegionVision;
file << holdFire;
file << movementAccel;
file << bonusShield;
file << supplyConsumeFactor;
file << bonusEffectiveness;
}
void load(Ship& ship, SaveFile& file) {
timer = -float(uint8(ship.id)) / 255.f;
bpTimer = timer - 0.5f;
loadObjectStates(ship, file);
file >> cast<Savable>(ship.Mover);
bool has = false;
file >> has;
if(has) {
ship.activateLeaderAI();
file >> cast<Savable>(ship.LeaderAI);
}
file >> has;
if(has) {
ship.activateSupportAI();
file >> cast<Savable>(ship.SupportAI);
}
file >> has;
if(has) {
ship.activateAbilities();
file >> cast<Savable>(ship.Abilities);
}
file >> has;
if(has) {
ship.activateStatuses();
file >> cast<Savable>(ship.Statuses);
}
if(file >= SV_0122) {
file >> has;
if(has) {
ship.activateCargo();
file >> cast<Savable>(ship.Cargo);
}
}
if(file >= SV_0108) {
file >> has;
if(has) {
ship.activateConstruction();
file >> cast<Savable>(ship.Construction);
}
}
if(file >= SV_0060) {
file >> has;
if(has) {
ship.activateOrbit();
file >> cast<Savable>(ship.Orbit);
}
}
file >> currentMaintenance;
file >> ship.isFree;
file >> ship.isFTLing;
file >> ship.delayFTL;
file >> prevSupply;
file >> ship.Energy;
file >> ship.MaxEnergy;
file >> ship.Supply;
file >> ship.MaxSupply;
file >> ship.formationDest;
ship.blueprint.load(ship, file);
@ship.Leader = file.readObject();
file >> ship.RetrofittingAt;
file >> retrofitTo;
file >> queuedRetrofit;
file >> suppression;
file >> wreckage;
file >> onFire;
if(file >= SV_0077)
file >> supplyBonus;
if(file >= SV_0081)
file >> massBonus;
if(file >= SV_0092) {
file >> ship.Shield;
file >> ship.MaxShield;
file >> shieldRegen;
}
if(file >= SV_0104) {
file >> disableRegionVision;
file >> holdFire;
}
if(file >= SV_0117)
file >> movementAccel;
getDesignMesh(ship.owner, ship.blueprint.design, shipMesh);
bindMesh(ship, shipMesh);
if(file >= SV_0138)
file >> bonusShield;
if(file >= SV_0147)
file >> supplyConsumeFactor;
if(file >= SV_0149)
file >> bonusEffectiveness;
if(ship.hasSupportAI)
ship.supportPostLoad();
}
bool get_isStation(Ship& ship) {
return ship.blueprint.design.hasTag(ST_Station);
}
void setHoldFire(bool value) {
if(value)
holdFire += 1;
else
holdFire -= 1;
}
bool getHoldFire() {
return holdFire > 0 || inFTL;
}
void setDisableRegionVision(bool value) {
if(value)
disableRegionVision += 1;
else
disableRegionVision -= 1;
}
bool getDisableRegionVision() {
return disableRegionVision > 0;
}
void addBonusEffectiveness(float mod) {
bonusEffectiveness += mod;
}
void modMass(Ship& ship, float amount) {
massBonus += amount;
ship.blueprint.statusID++;
}
float getMass() {
return max(mass + massBonus, 0.01f);
}
float getBaseMass() {
return max(mass, 0.01f);
}
void modHPFactor(Ship& ship, float pct) {
auto@ bp = ship.blueprint;
bp.hpFactor += pct;
bp.delta = true;
}
void modBonusShield(Ship& ship, float mod) {
bonusShield += mod;
ship.blueprint.statusID++;
}
void shieldDamage(Ship& ship, double amount) {
ship.Shield = clamp(ship.Shield - amount, 0.0, max(ship.MaxShield, ship.Shield));
}
void postLoad(Ship& ship) {
if(ship.hasLeaderAI) {
ship.leaderPostLoad();
Node@ node = ship.getNode();
if(node !is null)
node.animInvis = true;
}
cacheStats(ship);
updateStats(ship);
if(ship.region !is null) {
Node@ node = ship.getNode();
if(node !is null)
node.hintParentObject(ship.region, false);
}
}
void init(Ship& ship) {
timer = -float(uint8(ship.id)) / 255.f;
bpTimer = timer - 0.5f;
}
uint moneyType(Ship& ship) {
const Design@ dsg = ship.blueprint.design;
if(dsg !is null && dsg.hasTag(ST_Station))
return MoT_Orbitals;
return MoT_Ships;
}
stat::EmpireStat statType(Ship& ship) {
const Design@ dsg = ship.blueprint.design;
if(dsg !is null && dsg.hasTag(ST_Station))
return stat::Stations;
return stat::Ships;
}
void postInit(Ship& ship) {
cacheStats(ship);
updateStats(ship, true);
if(ship.hasLeaderAI)
ship.Supply = ship.MaxSupply;
else
ship.Supply = 0;
if(ship.hasLeaderAI) {
ship.leaderInit();
ship.activateStatuses();
ship.activateAbilities();
if(ship.owner.valid)
ship.owner.recordStatDelta(statType(ship), 1);
auto@ node = ship.getNode();
if(node !is null)
node.animInvis = true;
}
else{
ship.supportInit();
}
if(ship.owner !is null && ship.owner.valid) {
if(ship.hasLeaderAI)
ship.owner.points += int(double(ship.blueprint.design.size) * SHIP_SIZE_POINTS);
if(ship.hasLeaderAI) {
ship.owner.TotalFlagshipsBuilt += 1;
ship.owner.TotalFlagshipsActive += 1;
}
else {
ship.owner.TotalSupportsBuilt += 1;
ship.owner.TotalSupportsActive += 1;
}
}
ship.startEffects();
}
void startEffects(Ship& ship) {
if(ship.blueprint.design.hasTag(ST_Ability)) {
if(!ship.hasAbilities)
ship.activateAbilities();
ship.initAbilities(ship.blueprint.design);
}
ship.blueprint.start(ship);
}
int get_maintenanceCost() {
return currentMaintenance;
}
void setHealthPct(Ship& obj, float pct) {
if(pct >= 0.999f)
return;
auto@ bp = obj.blueprint;
vec2i size = bp.design.hull.gridSize;
DamageEvent evt;
evt.pierce = 0.f;
@evt.obj = obj;
@evt.target = obj;
for(int x = 0; x < size.x; ++x) {
for(int y = 0; y < size.y; ++y) {
auto@ status = bp.getHexStatus(x, y);
if(status !is null && status.hp != 0) {
evt.damage = bp.design.variable(vec2u(x,y), HV_HP) * (1.f - pct);
if(evt.damage > 0.001f)
bp.damage(obj, evt, vec2u(x, y));
}
}
}
needRepair = true;
bp.delta = true;
}
void makeNotFree(Ship& ship) {
if(ship.isFree) {
if(ship.owner !is null && ship.owner.valid) {
currentMaintenance = max(ship.blueprint.design.total(HV_MaintainCost), 0.0);
ship.owner.modMaintenance(currentMaintenance, moneyType(ship));
}
else {
currentMaintenance = 0;
}
ship.isFree = false;
}
}
void startRetrofit(Ship& ship, Object@ from, const Design@ to) {
if(ship.RetrofittingAt !is null)
return;
@ship.RetrofittingAt = from;
@retrofitTo = to;
}
void stopRetrofit(Ship& ship, Object@ from) {
if(ship.RetrofittingAt is from) {
@ship.RetrofittingAt = null;
@retrofitTo = null;
}
}
void completeRetrofit(Ship& ship, Object@ from) {
if(ship.RetrofittingAt is from) {
makeNotFree(ship);
@queuedRetrofit = retrofitTo;
@ship.RetrofittingAt = null;
@retrofitTo = null;
}
}
void cacheStats(Ship& ship) {
ship.MaxDPS = ship.blueprint.design.total(SV_DPS);
if(ship.hasLeaderAI)
ship.MaxSupply = ship.blueprint.design.total(SV_SupplyCapacity) + supplyBonus;
else
ship.MaxSupply = ship.blueprint.design.total(SV_SupportSupplyCapacity) + supplyBonus;
mass = ship.blueprint.design.total(HV_Mass);
/*ship.MaxEnergy = ship.blueprint.design.total(SV_EnergyCapacity);*/
ship.MaxEnergy = 0;
const Design@ dsg = ship.blueprint.design;
ship.hasVectorMovement = ship.isStation || (dsg.hasTag(ST_VectorThrust) && !dsg.hasTag(ST_TurnToThrust));
}
void modSupplyBonus(Ship& ship, float amount) {
supplyBonus += amount;
if(ship.hasLeaderAI)
ship.MaxSupply = ship.blueprint.design.total(SV_SupplyCapacity) + supplyBonus;
else
ship.MaxSupply = ship.blueprint.design.total(SV_SupportSupplyCapacity) + supplyBonus;
if(ship.MaxSupply > 0 && amount < 0)
ship.Supply += (ship.Supply / ship.MaxSupply) * amount;
}
float curThrust = 0.f;
float curTurnThrust = 0.f;
void updateAccel(Ship& ship) {
float thrust = curThrust;
if(ship.hasLeaderAI) {
float leaderAccel = thrust / max(mass + massBonus, 0.01f);
float supportAccel = ship.slowestSupportAccel;
float resultAccel = leaderAccel;
if(supportAccel > 0.0f && supportAccel * 0.75f < leaderAccel)
resultAccel = supportAccel * 0.75f;
if(resultAccel > movementAccel || leaderAccel < movementAccel || !ship.isMoving) {
ship.maxAcceleration = resultAccel;
movementAccel = resultAccel;
}
}
else {
if(ship.isRaiding)
thrust += ship.blueprint.getEfficiencySum(SV_BoostThrust);
float accel = thrust / max(mass + massBonus, 0.01f);
ship.maxAcceleration = accel;
}
}
void updateStats(Ship& ship, bool init = false) {
const Design@ dsg = ship.blueprint.design;
//Set the mover's maximum acceleration based on thrust
curThrust = ship.blueprint.getEfficiencySum(SV_Thrust);
curTurnThrust = ship.blueprint.getEfficiencySum(SV_TurnThrust);
if(curTurnThrust != 0)
ship.rotationSpeed = max(curTurnThrust / max(mass, 0.01f), 0.005f);
if(init)
ship.maxAcceleration = curThrust / max(mass + massBonus, 0.01f);
else
updateAccel(ship);
//Record the used command
float commandUsed = dsg.variable(ShV_REQUIRES_Command);
float powerUsed = dsg.variable(ShV_REQUIRES_Power);
//Record DPS
ship.DPS = ship.blueprint.getEfficiencySum(SV_DPS);
//Update shield stats
double maxShield = ship.blueprint.getEfficiencySum(SV_ShieldCapacity);
if(maxShield > 0)
maxShield += bonusShield;
if(maxShield != ship.MaxShield) {
if(maxShield == 0) {
ship.Shield = 0;
ship.MaxShield = 0;
shieldRegen = 0;
}
else if(ship.MaxShield > 0) {
ship.Shield = maxShield * (ship.Shield / ship.MaxShield);
ship.MaxShield = maxShield;
shieldRegen = ship.blueprint.getEfficiencySum(SV_ShieldRegen);
}
else {
ship.MaxShield = maxShield;
shieldRegen = ship.blueprint.getEfficiencySum(SV_ShieldRegen);
}
shieldDelta = true;
}
//Set the supply capacity of the ship
int supply = ship.blueprint.getEfficiencySum(SV_SupportCapacity);
if(supply != prevSupply) {
ship.modSupplyCapacity(supply - prevSupply);
prevSupply = supply;
}
//Modify ship efficiency based on available command
float commandAvail = ship.blueprint.getEfficiencySum(SV_Command);
if(commandAvail >= commandUsed || commandUsed <= 0.0)
shipEffectiveness = 1.f;
else if(commandAvail == 0.f && dsg.total(SV_Command) == 0.f)
shipEffectiveness = 1.f;
else
shipEffectiveness = float(commandAvail) / commandUsed;
shipEffectiveness *= (1.f + bonusEffectiveness);
//Low power also decreases effectiveness
if(powerUsed > 0) {
float powerAvail = ship.blueprint.getEfficiencySum(SV_Power);
if(powerAvail < powerUsed)
shipEffectiveness *= sqr(powerAvail / powerUsed);
}
float effectiveness = shipEffectiveness;
if(suppression > 0.f)
effectiveness /= 1.f + suppression;
{
double minRange = INFINITY, maxRange = -INFINITY;
double minRaid = INFINITY, maxRaid = -INFINITY;
for(uint i = 0, cnt = dsg.subsystemCount; i < cnt; ++i) {
auto@ subsys = dsg.subsystems[i];
if(subsys.type.hasTag(ST_IgnoreRange))
continue;
bool forRaid = subsys.type.hasTag(ST_RangeForRaid);
for(uint j = 0, jcnt = subsys.effectorCount; j < jcnt; ++j) {
double range = subsys.effectors[j].range;
if(!forRaid) {
if(range < minRange)
minRange = range;
if(range > maxRange)
maxRange = range;
}
if(range < minRaid)
minRaid = range;
if(range > maxRaid)
maxRaid = range;
}
}
if(!ship.hasLeaderAI)
ship.supportEngageRange = min(minRange, minRaid);
ship.minEngagementRange = minRange;
ship.maxEngagementRange = maxRange;
}
if(!ship.hasLeaderAI) {
Object@ leader = ship.Leader;
if(leader !is null)
effectiveness *= leader.getFleetEffectiveness();
else
effectiveness *= SUPPLY_EFFICIENCY;
ship.blueprint.shipEffectiveness = effectiveness;
}
else {
ship.blueprint.shipEffectiveness = double(effectiveness) * double(ship.getFleetEffectiveness());
}
//Store the amount of repair we have available
currentRepair = ship.blueprint.getEfficiencySum(SV_Repair);
currentRepairCost = ship.blueprint.getEfficiencySum(SV_RepairSupplyCost);
//Check if we should update our maintenance
if(!ship.isFree && ship.owner !is null && ship.owner.valid) {
int maint = max(ship.blueprint.design.total(HV_MaintainCost), 0.0);
if(maint != currentMaintenance) {
ship.owner.modMaintenance(maint - currentMaintenance, moneyType(ship));
currentMaintenance = maint;
}
}
bpStatusID = ship.blueprint.statusID;
}
void scuttle(Ship& ship) {
ship.destroy();
}
Object@ getLastHitBy() {
return lastHitBy;
}
void destroy(Ship& ship) {
if(ship.owner !is null && ship.owner.valid && currentMaintenance != 0)
ship.owner.modMaintenance(-currentMaintenance, moneyType(ship));
if(ship.inCombat) {
double regain = ship.owner.ShipCostRegain;
if(regain > 0.001) {
regain = floor(regain * getBuildCost(ship.blueprint.design));
if(regain >= 1.0)
ship.owner.addBonusBudget(int(regain));
}
}
//Assuming we've been hit recently, the likely cause of death is explosion
if(killCredit !is null && !game_ending) {
auto size = ship.blueprint.design.size;
if(size < 16)
playParticleSystem("ShipExplosionLight", ship.position, ship.rotation, ship.radius, ship.visibleMask);
else
playParticleSystem("ShipExplosion", ship.position, ship.rotation, ship.radius, ship.visibleMask);
if(size > 64)
playParticleSystem("ShipExplosionExtra", ship.position, ship.rotation, ship.radius, ship.visibleMask);
if(size >= 200)
playParticleSystem("ShipExplosionLong", ship.position, ship.rotation, ship.radius, ship.visibleMask);
auto@ region = ship.region;
if(region !is null) {
uint debris = uint(log(size) / log(2.0));
if(debris > 0)
region.addShipDebris(ship.position, debris);
if(killCredit !is ship.owner && ship.hasLeaderAI)
region.grantExperience(killCredit, size * config::EXPERIENCE_GAIN_FACTOR, combatOnly=true);
}
if(ship.hasLeaderAI && currentMaintenance != 0) {
Empire@ master = killCredit.SubjugatedBy;
if(master !is null && master.getEdictType() == DET_Conquer) {
if(master.getEdictEmpire() is ship.owner) {
giveRandomReward(killCredit, double(currentMaintenance) / 100.0);
}
}
double reward = killCredit.DestroyShipReward + ship.owner.ShipDestroyBounty;
if(reward > 0.001) {
reward = floor(reward * currentMaintenance);
if(reward >= 1.0)
killCredit.addBonusBudget(int(reward));
}
if(ship.owner.major && ship.getFleetMaxStrength() >= 1000.0)
killCredit.modAttribute(EA_EnemyFlagshipsDestroyed, AC_Add, 1.0);
}
}
if(ship.owner !is null && ship.owner.valid) {
if(ship.hasLeaderAI) {
ship.owner.points -= int(double(ship.blueprint.design.size) * SHIP_SIZE_POINTS);
ship.owner.recordStatDelta(statType(ship), -1);
}
if(killCredit !is ship.owner)
ship.owner.recordStatDelta(stat::ShipsLost, 1);
}
if(lastHitBy !is null && lastHitBy !is ship)
ship.lastHit = lastHitBy.id;
if(ship.hasSupportAI) {
ship.supportDestroy();
if(ship.owner !is null)
ship.owner.TotalSupportsActive -= 1;
}
else if(ship.hasLeaderAI) {
ship.leaderDestroy();
if(ship.owner !is null)
ship.owner.TotalFlagshipsActive -= 1;
}
ship.blueprint.destroy(ship);
if(ship.hasStatuses)
ship.destroyStatus();
if(ship.hasConstruction)
ship.destroyConstruction();
if(ship.hasAbilities)
ship.destroyAbilities();
if(killCredit !is null && killCredit !is ship.owner && killCredit.valid)
killCredit.recordStatDelta(stat::ShipsDestroyed, 1);
leaveRegion(ship);
}
bool onOwnerChange(Ship& ship, Empire@ prevOwner) {
if(prevOwner !is null && prevOwner.valid) {
if(currentMaintenance != 0)
prevOwner.modMaintenance(-currentMaintenance, moneyType(ship));
if(ship.hasLeaderAI) {
prevOwner.TotalFlagshipsActive -= 1;
prevOwner.recordStatDelta(statType(ship), -1);
prevOwner.points -= int(double(ship.blueprint.design.size) * SHIP_SIZE_POINTS);
}
else
prevOwner.TotalSupportsActive -= 1;
}
if(ship.owner !is null && ship.owner.valid) {
if(currentMaintenance != 0)
ship.owner.modMaintenance(currentMaintenance, moneyType(ship));
if(ship.hasLeaderAI) {
ship.owner.TotalFlagshipsActive += 1;
ship.owner.recordStatDelta(statType(ship), 1);
ship.owner.points += int(double(ship.blueprint.design.size) * SHIP_SIZE_POINTS);
}
else
ship.owner.TotalSupportsActive += 1;
}
if(ship.hasAbilities)
ship.abilityOwnerChange(prevOwner, ship.owner);
if(ship.hasStatuses)
ship.changeStatusOwner(prevOwner, ship.owner);
regionOwnerChange(ship, prevOwner);
if(ship.hasLeaderAI)
ship.leaderChangeOwner(prevOwner, ship.owner);
ship.blueprint.ownerChange(ship, prevOwner, ship.owner);
return false;
}
bool consumeEnergy(Ship& ship, double amount) {
if(ship.Energy < amount)
return false;
ship.Energy -= amount;
return true;
}
bool consumeMinSupply(Ship& ship, double amount) {
if(ship.Supply >= amount) {
ship.Supply = max(0.0, ship.Supply - amount);
barDelta = true;
return true;
}
else {
return false;
}
}
void modSupplyConsumeFactor(float mod) {
supplyConsumeFactor += mod;
}
void consumeSupply(Ship& ship, double amount) {
if(ship.hasLeaderAI) {
ship.Supply = max(0.0, ship.Supply - amount * max(supplyConsumeFactor, 0.f));
barDelta = true;
}
else {
if(ship.isDetached) {
ship.Supply = max(0.0, ship.Supply - amount * max(supplyConsumeFactor, 0.f));
barDelta = true;
}
else {
Ship@ lead = cast<Ship>(cachedLeader);
if(lead !is null) {
lead.consumeSupply(amount * max(supplyConsumeFactor, 0.f));
return;
}
}
}
}
void consumeSupplyPct(Ship& ship, double pct) {
ship.Supply = max(0.0, ship.Supply - pct * ship.MaxSupply * max(supplyConsumeFactor, 0.f));
barDelta = true;
}
void refundEnergy(Ship& ship, double amount) {
ship.Energy = min(ship.MaxEnergy, ship.Energy + amount);
barDelta = true;
}
void refundSupply(Ship& ship, double amount) {
ship.Supply = min(ship.MaxSupply, ship.Supply + amount);
barDelta = true;
}
void repairShip(Ship& ship, double amount) {
ship.blueprint.repair(ship, amount);
}
void mangle(double amount) {
wreckage += amount;
}
void suppress(double amount) {
suppression += amount;
}
void startFire() {
onFire = true;
}
void triggerLeaderChange(Ship& ship, Object@ prevLeader, Object@ newLeader) {
if(ship.blueprint.design.dataCount != 0) {
uint hookN = 0;
SubsystemEvent evt;
@evt.obj = ship;
@evt.design = ship.blueprint.design;
@evt.blueprint = ship.blueprint;
for(uint i = 0, cnt = ship.blueprint.design.subsystemCount; i < cnt; ++i) {
auto@ subsys = ship.blueprint.design.subsystems[i];
for(uint n = 0, ncnt = subsys.hookCount; n < ncnt; ++n) {
auto@ cls = cast<LeaderChangeable>(subsys.hooks[n]);
if(cls !is null) {
@evt.subsystem = subsys;
@evt.data = ship.blueprint.getHookData(hookN);
cls.leaderChange(evt, prevLeader, newLeader);
}
hookN += 1;
}
}
}
}
float combatTimer = 0.f;
bool prevEngaged = false;
bool inFTL = false;
void occasional_tick(Ship& ship, float time) {
Empire@ owner = ship.owner;
Region@ reg = ship.region;
Ship@ shipLeader = cast<Ship>(cachedLeader);
bool regionChanged = updateRegion(ship, takeVision=false);
inFTL = ship.inFTL;
if(regionChanged) {
if(ship.hasLeaderAI) {
ship.leaderRegionChanged();
if(ship.hasStatuses)
ship.changeStatusRegion(reg, ship.region);
}
else {
Node@ node = ship.getNode();
if(node !is null)
node.hintParentObject(ship.region, false);
}
if(ship.blueprint.design.dataCount != 0) {
uint hookN = 0;
SubsystemEvent evt;
@evt.obj = ship;
@evt.design = ship.blueprint.design;
@evt.blueprint = ship.blueprint;
for(uint i = 0, cnt = ship.blueprint.design.subsystemCount; i < cnt; ++i) {
auto@ subsys = ship.blueprint.design.subsystems[i];
for(uint n = 0, ncnt = subsys.hookCount; n < ncnt; ++n) {
auto@ cls = cast<RegionChangeable>(subsys.hooks[n]);
if(cls !is null) {
@evt.subsystem = subsys;
@evt.data = ship.blueprint.getHookData(hookN);
cls.regionChange(evt, reg, ship.region);
}
hookN += 1;
}
}
}
@reg = ship.region;
}
//Take vision from region
if(reg !is null) {
if(disableRegionVision <= 0 && (shipLeader is null|| !shipLeader.getDisableRegionVision()))
ship.donatedVision |= reg.DonateVisionMask;
}
//Update in combat flags
bool engaged = ship.engaged;
if(cachedLeader !is null) {
if(engaged)
cachedLeader.engaged = true;
if(cachedLeader.inCombat)
engaged = true;
}
if(holdFire > 0 || inFTL || (shipLeader !is null && (shipLeader.getHoldFire() || (ship.isDetached && ship.Supply <= 0.001))))
ship.blueprint.holdFire = true;
else
ship.blueprint.holdFire = false;
if(engaged)
combatTimer = 5.f;
else
combatTimer -= time;
{
bool nowCombat = combatTimer > 0.f;
if(ship.inCombat != nowCombat) {
ship.inCombat = nowCombat;
barDelta = true;
}
}
ship.engaged = false;
//Deal with combat facing
Object@ target = ship.getAttackTarget();
if(engaged || target !is null) {
if(target is null)
@target = ship.blueprint.getCombatTarget();
if(target is null)
@target = findEnemy(ship, ship, owner, ship.position, 500.0);
if(target !is null) {
//Always face the fleet as a whole
{
Ship@ othership = cast<Ship>(target);
if(othership !is null) {
Object@ leader = othership.Leader;
if(leader !is null)
@target = leader;
}
}
//Find optimal facing
vec3d facing = ship.blueprint.getOptimalFacing(SV_DPS);
vec3d diff = target.position - ship.position;
diff.normalize();
//Rotate so that target rotation * facing = diff
quaterniond rot = quaterniond_fromVecToVec(facing, diff);
ship.setCombatFacing(rot);
if(ship.hasLeaderAI) {
//Order a random support to assist
uint cnt = ship.supportCount;
if(cnt > 0) {
uint attackWith = max(1, cnt / 8);
for(uint i = 0, off = randomi(0,cnt-1); i < attackWith; ++i) {
Object@ sup = ship.supportShip[(i+off) % cnt];
if(sup !is null)
sup.supportAttack(target);
}
}
}
}
else
ship.clearCombatFacing();
if(reg !is null)
reg.EngagedMask |= owner.mask;
}
else if(prevEngaged) {
ship.clearCombatFacing();
}
prevEngaged = engaged;
bool isContested = engaged || (reg !is null && reg.ContestedMask & owner.mask != 0);
//Deal with energy charge
float fleetEffectiveness = 1.f;
if(ship.MaxEnergy > 0 && ship.Energy < ship.MaxEnergy) {
if((reg !is null && reg.TradeMask & owner.TradeMask.value != 0 && !engaged)
|| owner.GlobalCharge) {
/*float chargeRate = ship.blueprint.getEfficiencySum(SV_ChargeRate);*/
float chargeRate = 0.f;
float amt = min(chargeRate * time, ship.MaxEnergy - ship.Energy);
amt = owner.consumeEnergy(amt);
if(amt > 0.f) {
ship.Energy = min(ship.Energy + amt, ship.MaxEnergy);
barDelta = true;
}
}
}
if(ship.hasSupportAI) {
ship.supportTick(time);
if(cachedLeader !is null)
ship.seeableRange = cachedLeader.seeableRange;
else
ship.seeableRange = FLOAT_INFINITY;
}
else if(ship.hasLeaderAI) {
ship.commandTick();
if(ship.Energy > 0) {
//Calculate extra fleet effectiveness based on energy
//relative to ship size and supply.
float size = ship.blueprint.design.size;
fleetEffectiveness *= 1.f + ENERGY_EFFECTIVENESS * (ship.Energy / size);
}
if(ship.MaxSupply > 0) {
//Supply recharge
if(ship.Supply < ship.MaxSupply) {
float chargeRate = ship.blueprint.getEfficiencySum(SV_SupplyRate);
float amt = chargeRate * time;
if(isContested || reg is null) {
amt = 0.f;
}
else if(reg.TradeMask & owner.TradeMask.value == 0) {
if(reg.isTradableRegion(owner))
amt *= SUPPLY_TRADE_RATE;
else
amt *= SUPPLY_UNOWNED_RATE;
}
if(amt > 0) {
ship.Supply = min(ship.MaxSupply, ship.Supply + amt);
barDelta = true;
}
}
//Supply effectiveness
float sup = ship.Supply / ship.MaxSupply;
if(sup < SUPPLY_THRESHOLD)
fleetEffectiveness *= SUPPLY_EFFICIENCY + sqrt(sup/SUPPLY_THRESHOLD) * (1.f - SUPPLY_EFFICIENCY);
}
else {
fleetEffectiveness *= SUPPLY_EFFICIENCY;
}
//Efficiency decrease due to debt
float debtFactor = ship.owner.DebtFactor;
if(debtFactor > 1.f)
fleetEffectiveness *= pow(0.5f, debtFactor-1.f);
fleetEffectiveness *= owner.FleetEfficiencyFactor;
ship.setFleetEffectiveness(fleetEffectiveness);
}
updateAccel(ship);
float effectiveness = shipEffectiveness;
float prevEffectiveness = ship.blueprint.shipEffectiveness;
//Deal with fleet effectiveness
if(ship.hasLeaderAI) {
effectiveness *= ship.getFleetEffectiveness();
ship.blueprint.shipEffectiveness = effectiveness;
ship.updateFleetStrength();
}
else {
Object@ leader = ship.Leader;
if(leader !is null)
effectiveness *= leader.getFleetEffectiveness();
else
effectiveness *= SUPPLY_EFFICIENCY;
ship.blueprint.shipEffectiveness = effectiveness;
}
if(prevEffectiveness != ship.blueprint.shipEffectiveness)
ship.blueprint.delta = true;
if(suppression > 0.f) {
//Apply suppression before recovery, or small suppression will never do anything
effectiveness /= 1.f + suppression;
suppression -= (1.f + suppression) * SUPPRESSION_REGEN * time;
if(suppression < 0.f)
suppression = 0.f;
}
if(onFire && time > 0) {
double fireDamage = ship.blueprint.design.size * FIRE_DPS * time * owner.FireDamageTakenFactor;
internalDamage(ship, fireDamage);
if(effectiveness > 0 && randomd() < pow(EXTINGUISH_CHANCE, 1.0 / (time * effectiveness)))
onFire = false;
}
//Clear kill credits after short spans of time
if(killCredit !is null && !ship.inCombat) {
@killCredit = null;
@lastHitBy = null;
}
//Check if we can retrofit
if(queuedRetrofit !is null && !isContested) {
retrofit(ship, queuedRetrofit);
@queuedRetrofit = null;
}
@cachedLeader = ship.Leader;
}
double tick(Ship& ship, double time) {
auto@ bp = ship.blueprint;
if(bp.statusID != bpStatusID)
updateStats(ship);
bool inCombat = ship.inCombat;
//Keep a timer for some occasional checks
timer += float(time);
if(timer >= 1.f) {
occasional_tick(ship, timer);
timer = 0.f;
}
double delay = 0.2;
double d = 0.2;
if(inCombat) {
bpTimer += time;
if(bpTimer > 0.2f) {
d = bp.tick(ship, bpTimer);
bpTimer = 0.f;
}
}
else {
bpTimer += time;
if(bpTimer >= 1.f) {
d = bp.tick(ship, bpTimer);
bpTimer = 0.f;
}
}
if(!ship.hasSupportAI) {
if(ship.hasLeaderAI) {
ship.orderTick(time);
ship.leaderTick(time);
}
if(ship.hasAbilities)
ship.abilityTick(time);
if(ship.hasStatuses)
ship.statusTick(time);
if(ship.hasConstruction)
ship.constructionTick(time);
}
if(d < delay)
delay = d;
d = ship.moverTick(time);
if(d < delay)
delay = d;
if(ship.Shield < ship.MaxShield) {
ship.Shield = min(ship.Shield + shieldRegen * time, ship.MaxShield);
shieldDelta = true;
}
//Repair out of combat
double damage = bp.design.totalHP - (bp.currentHP + bp.removedHP);
if(currentRepair > 0.f && (damage > 0.f || wreckage > 0.f)) {
double repairFact = 1.0;
bool inCombat = ship.inCombat;
if(inCombat) {
repairFact *= COMBAT_REPAIR_MOD;
repairFact *= min(bp.shipEffectiveness, 1.0);
}
if(repairFact != 0) {
double repairAmt = currentRepair * repairFact * time;
double repairCost = currentRepairCost * repairFact * time;
if(inCombat) {
if(repairCost < 0) {
repairAmt = 0;
}
else if(cachedLeader !is null) {
Ship@ shipLeader = cast<Ship>(cachedLeader);
if(shipLeader !is null) {
if(shipLeader.Supply < repairCost)
repairAmt = 0.0;
else
shipLeader.consumeSupply(repairCost);
}
}
else {
if(ship.Supply < repairCost)
repairAmt = 0.0;
else
consumeSupply(ship, repairCost);
}
}
else {
repairAmt = max(repairAmt, 0.01 * bp.design.totalHP * repairFact * time);
}
if(repairAmt != 0) {
if(wreckage > 0.f) {
double repWreckage = repairAmt * wreckage / (wreckage + damage);
repairAmt -= repWreckage;
wreckage -= repWreckage;
}
bp.repair(ship, repairAmt);
}
}
}
else {
bp.repairingHex.x = -1;
bp.repairingHex.y = -1;
needRepair = false;
}
return delay;
}
void internalDamage(Ship& ship, double amount) {
Blueprint@ bp = ship.blueprint;
const Design@ dsg = bp.design;
vec2i gridSize = dsg.hull.gridSize;
vec2i base = vec2i(randomi(0, gridSize.x-1), randomi(0, gridSize.y-1));
for(int x = 0; x < gridSize.x; ++x) {
for(int y = 0; y < gridSize.y; ++y) {
vec2u pos = vec2u((base.x + x) % gridSize.x, (base.y + y) % gridSize.y);
auto status = bp.getHexStatus(pos.x,pos.y);
if(status is null || status.hp == 0)
continue;
DamageEvent evt;
evt.damage = amount;
bp.damage(ship, evt, pos);
needRepair = true;
return;
}
}
}
void recordDamage(Ship& ship, Object@ source) {
if(source !is null) {
if(lastHitBy !is source && ship.hasLeaderAI) {
//Order a random support to block, and another to attack
uint cnt = ship.supportCount;
if(cnt > 0) {
uint ind = randomi(0,cnt-1);
Object@ sup = ship.supportShip[ind];
if(sup !is null)
sup.supportInterfere(lastHitBy, ship);
if(cnt > 1) {
@sup = ship.supportShip[ind+1];
if(sup !is null)
sup.supportAttack(lastHitBy);
}
}
}
@lastHitBy = source;
@killCredit = source.owner;
}
}
void damage(Ship& ship, DamageEvent& evt, double position, const vec2d& direction) {
//Score kills to the last aggressor
double prevShield = ship.Shield;
if(evt.obj !is null) {
if(lastHitBy !is evt.obj && ship.hasLeaderAI) {
//Order a random support to block, and another to attack
uint cnt = ship.supportCount;
if(cnt > 0) {
uint ind = randomi(0,cnt-1);
Object@ sup = ship.supportShip[ind];
if(sup !is null)
sup.supportInterfere(lastHitBy, ship);
if(cnt > 1) {
@sup = ship.supportShip[ind+1];
if(sup !is null)
sup.supportAttack(lastHitBy);
}
}
}
@lastHitBy = evt.obj;
@killCredit = evt.obj.owner;
}
ship.engaged = true;
ship.blueprint.damage(ship, evt, direction);
needRepair = true;
if(prevShield != ship.Shield)
shieldDelta = true;
if(ship.blueprint.currentHP <= 0.01) {
//This shouldn't be needed, but somehow it is
ship.destroy();
}
}
void damageAllHexes(Ship& ship, double damage, Object@ source = null) {
Blueprint@ bp = ship.blueprint;
if(bp is null)
return;
const Design@ dsg = bp.design;
if(dsg is null)
return;
DamageEvent evt;
@evt.obj = ship;
@evt.target = ship;
evt.partiality = 1.0;
for(uint i = 0, cnt = dsg.subsystemCount; i < cnt; ++i) {
auto@ subsys = dsg.subsystems[i];
if(!subsys.has(HV_HP))
continue;
for(uint n = 0, ncnt = subsys.hexCount; n < ncnt; ++n) {
vec2u pos = subsys.hexagon(n);
evt.damage = damage;
evt.partiality = 1.0;
bp.damageHex(ship, evt, pos, false);
}
}
if(source !is null)
recordDamage(ship, source);
}
void restoreShield(Ship& ship, double value) {
double prevShield = ship.Shield;
ship.Shield = clamp(ship.Shield + value, 0, ship.MaxShield);
if(ship.Shield != prevShield)
shieldDelta = true;
}
Empire@ getKillCredit() {
return killCredit;
}
void syncInitial(const Ship& ship, Message& msg) {
msg.writeSmall(ship.blueprint.design.hull.id);
ship.blueprint.sendDetails(ship, msg);
if(ship.hasLeaderAI) {
msg.write1();
ship.writeLeaderAI(msg);
}
else {
msg.write0();
ship.writeSupportAI(msg);
}
ship.writeMover(msg);
if(ship.MaxEnergy > 0) {
msg.write1();
msg << ship.MaxEnergy;
msg.writeFixed(ship.Energy, 0.f, ship.MaxEnergy, 16);
}
else {
msg.write0();
}
if(ship.MaxSupply > 0) {
msg.write1();
msg << ship.MaxSupply;
msg.writeFixed(ship.Supply, 0.f, ship.MaxSupply, 16);
}
else {
msg.write0();
}
if(ship.MaxShield > 0) {
msg.write1();
msg << ship.MaxShield;
msg.writeFixed(ship.Shield, 0.f, ship.MaxShield, 16);
}
else {
msg.write0();
}
if(ship.hasAbilities) {
msg.write1();
ship.writeAbilities(msg);
}
else {
msg.write0();
}
if(ship.hasStatuses) {
msg.write1();
ship.writeStatuses(msg);
}
else {
msg.write0();
}
if(ship.hasCargo) {
msg.write1();
ship.writeCargo(msg);
}
else {
msg.write0();
}
if(ship.hasConstruction) {
msg.write1();
ship.writeConstruction(msg);
}
else {
msg.write0();
}
if(ship.hasOrbit) {
msg.write1();
ship.writeOrbit(msg);
}
else {
msg.write0();
}
}
void retrofit(Ship& ship, const Design@ newDesign) {
const Design@ prevDesign = ship.blueprint.design;
ship.blueprint.retrofit(ship, newDesign);
cacheStats(ship);
ship.Energy = min(ship.Energy, ship.MaxEnergy);
if(ship.hasAbilities)
ship.initAbilities(ship.blueprint.design);
if(newDesign.base() !is prevDesign.base()) {
if(ship.hasLeaderAI) {
ship.recalculateLevels(prevDesign.size, newDesign.size);
}
else if(ship.hasSupportAI) {
Object@ leader = ship.Leader;
if(leader !is null)
leader.postSupportRetrofit(ship, prevDesign, newDesign);
}
}
ship.Supply = min(ship.Supply, ship.MaxSupply);
ship.Shield = min(ship.Shield, ship.MaxShield);
ship.compEngageRange();
barDelta = true;
}
void syncDetailed(const Ship& ship, Message& msg) {
ship.writeMover(msg);
if(ship.hasLeaderAI)
ship.writeLeaderAI(msg);
else
ship.writeSupportAI(msg);
ship.blueprint.sendDetails(ship, msg);
msg << ship.Energy;
msg << ship.MaxEnergy;
msg << ship.Supply;
msg << ship.MaxSupply;
msg << ship.Shield;
msg << ship.MaxShield;
msg.writeBit(ship.isFTLing);
msg.writeBit(ship.inCombat);
if(ship.hasAbilities)
ship.writeAbilities(msg);
if(ship.hasStatuses)
ship.writeStatuses(msg);
if(ship.hasCargo) {
msg.write1();
ship.writeCargo(msg);
}
else {
msg.write0();
}
if(ship.hasOrbit) {
msg.write1();
ship.writeOrbit(msg);
}
else {
msg.write0();
}
if(ship.hasConstruction) {
msg.write1();
ship.writeConstruction(msg);
}
else {
msg.write0();
}
}
bool prevFTL = false, prevCombat = false;
bool syncDelta(const Ship& ship, Message& msg) {
bool used = false;
if(ship.writeMoverDelta(msg))
used = true;
else
msg.write0();
if(ship.blueprint.sendDelta(ship, msg))
used = true;
else
msg.write0();
msg.writeBit(shieldDelta);
if(shieldDelta) {
used = true;
shieldDelta = false;
msg.writeFixed(ship.Shield, 0.f, ship.MaxShield, 16);
}
if(ship.hasLeaderAI) {
if(ship.writeLeaderAIDelta(msg))
used = true;
else
msg.write0();
}
else {
if(ship.writeSupportAIDelta(msg))
used = true;
else
msg.write0();
}
if(ship.hasAbilities) {
if(ship.writeAbilityDelta(msg))
used = true;
else
msg.write0();
}
if(ship.hasStatuses) {
if(ship.writeStatusDelta(msg))
used = true;
else
msg.write0();
}
if(ship.hasLeaderAI) {
if(ship.hasCargo) {
if(ship.writeCargoDelta(msg))
used = true;
else
msg.write0();
}
else {
msg.write0();
}
}
if(barDelta || prevFTL != ship.isFTLing
|| prevCombat != ship.inCombat) {
msg.write1();
used = true;
msg.writeBit(ship.Energy > 0);
if(ship.Energy > 0)
msg << ship.Energy;
msg.writeBit(ship.Supply > 0);
if(ship.Supply > 0)
msg << ship.Supply;
msg.writeBit(ship.isFTLing);
msg.writeBit(ship.inCombat);
barDelta = false;
prevFTL = ship.isFTLing;
prevCombat = ship.inCombat;
}
else {
msg.write0();
}
if(ship.hasOrbit) {
if(ship.writeOrbitDelta(msg))
used = true;
else
msg.write0();
}
else {
msg.write0();
}
if(ship.hasLeaderAI && ship.hasConstruction) {
if(ship.writeConstructionDelta(msg))
used = true;
else
msg.write0();
}
else {
msg.write0();
}
return used;
}
};