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(ship.Mover); if(ship.hasLeaderAI) { file << true; file << cast(ship.LeaderAI); } else { file << false; } if(ship.hasSupportAI) { file << true; file << cast(ship.SupportAI); } else { file << false; } if(ship.hasAbilities) { file << true; file << cast(ship.Abilities); } else { file << false; } if(ship.hasStatuses) { file << true; file << cast(ship.Statuses); } else { file << false; } if(ship.hasCargo) { file << true; file << cast(ship.Cargo); } else { file << false; } if(ship.hasConstruction) { file << true; file << cast(ship.Construction); } else { file << false; } if(ship.hasOrbit) { file << true; file << cast(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(ship.Mover); bool has = false; file >> has; if(has) { ship.activateLeaderAI(); file >> cast(ship.LeaderAI); } file >> has; if(has) { ship.activateSupportAI(); file >> cast(ship.SupportAI); } file >> has; if(has) { ship.activateAbilities(); file >> cast(ship.Abilities); } file >> has; if(has) { ship.activateStatuses(); file >> cast(ship.Statuses); } if(file >= SV_0122) { file >> has; if(has) { ship.activateCargo(); file >> cast(ship.Cargo); } } if(file >= SV_0108) { file >> has; if(has) { ship.activateConstruction(); file >> cast(ship.Construction); } } if(file >= SV_0060) { file >> has; if(has) { ship.activateOrbit(); file >> cast(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(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(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(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(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(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(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; } };