Removed background sector wave simulation
This commit is contained in:
@@ -822,13 +822,12 @@ sectors.redirect = Redirect Launch Pads
|
|||||||
sectors.rename = Rename Sector
|
sectors.rename = Rename Sector
|
||||||
sectors.enemybase = [scarlet]Enemy Base
|
sectors.enemybase = [scarlet]Enemy Base
|
||||||
sectors.vulnerable = [scarlet]Vulnerable
|
sectors.vulnerable = [scarlet]Vulnerable
|
||||||
sectors.underattack = [scarlet]Under attack! [accent]{0}% damaged
|
sectors.underattack = [scarlet]Under attack!
|
||||||
sectors.underattack.nodamage = [scarlet]Uncaptured
|
|
||||||
sectors.survives = [accent]Survives {0} waves
|
|
||||||
sectors.go = Go
|
sectors.go = Go
|
||||||
sector.abandon = Abandon
|
sector.abandon = Abandon
|
||||||
sector.abandon.confirm = This sector's core(s) will self-destruct.\nContinue?
|
sector.abandon.confirm = This sector's core(s) will self-destruct.\nContinue?
|
||||||
sector.curcapture = Sector Captured
|
sector.curcapture = Sector Captured
|
||||||
|
sector.lockdown = [red]:warning:[accent] Sector currently under attack\n[lightgray]production, research, export and import disabled
|
||||||
sector.curlost = Sector Lost
|
sector.curlost = Sector Lost
|
||||||
sector.missingresources = [scarlet]Insufficient Core Resources
|
sector.missingresources = [scarlet]Insufficient Core Resources
|
||||||
sector.attacked = Sector [accent]{0}[white] under attack!
|
sector.attacked = Sector [accent]{0}[white] under attack!
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ public class Planets{
|
|||||||
sectorSeed = 2;
|
sectorSeed = 2;
|
||||||
allowWaves = true;
|
allowWaves = true;
|
||||||
allowLegacyLaunchPads = true;
|
allowLegacyLaunchPads = true;
|
||||||
allowWaveSimulation = true;
|
|
||||||
allowSectorInvasion = true;
|
allowSectorInvasion = true;
|
||||||
allowLaunchSchematics = true;
|
allowLaunchSchematics = true;
|
||||||
enemyCoreSpawnReplace = true;
|
enemyCoreSpawnReplace = true;
|
||||||
@@ -157,7 +156,7 @@ public class Planets{
|
|||||||
landCloudColor = Pal.spore.cpy().a(0.5f);
|
landCloudColor = Pal.spore.cpy().a(0.5f);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
verilus = makeAsteroid("verlius", sun, Blocks.stoneWall, Blocks.iceWall, -1, 0.5f, 12, 2f, gen -> {
|
verilus = makeAsteroid("verilus", sun, Blocks.stoneWall, Blocks.iceWall, -1, 0.5f, 12, 2f, gen -> {
|
||||||
gen.berylChance = 0f;
|
gen.berylChance = 0f;
|
||||||
gen.iceChance = 0.6f;
|
gen.iceChance = 0.6f;
|
||||||
gen.carbonChance = 0.1f;
|
gen.carbonChance = 0.1f;
|
||||||
|
|||||||
@@ -77,37 +77,9 @@ public class Logic implements ApplicationListener{
|
|||||||
SectorInfo info = state.rules.sector.info;
|
SectorInfo info = state.rules.sector.info;
|
||||||
info.write();
|
info.write();
|
||||||
|
|
||||||
//only simulate waves if the planet allows it
|
|
||||||
if(state.rules.sector.planet.allowWaveSimulation){
|
|
||||||
//how much wave time has passed
|
|
||||||
int wavesPassed = info.wavesPassed;
|
|
||||||
|
|
||||||
//wave has passed, remove all enemies, they are assumed to be dead
|
|
||||||
if(wavesPassed > 0){
|
|
||||||
Groups.unit.each(u -> {
|
|
||||||
if(u.team == state.rules.waveTeam){
|
|
||||||
u.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//simulate passing of waves
|
|
||||||
if(wavesPassed > 0){
|
|
||||||
//simulate wave counter moving forward
|
|
||||||
state.wave += wavesPassed;
|
|
||||||
state.wavetime = state.rules.waveSpacing * state.getPlanet().campaignRules.difficulty.waveTimeMultiplier;
|
|
||||||
|
|
||||||
SectorDamage.applyCalculatedDamage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.getSector().planet.applyRules(state.rules);
|
state.getSector().planet.applyRules(state.rules);
|
||||||
|
|
||||||
//reset values
|
|
||||||
info.damage = 0f;
|
|
||||||
info.wavesPassed = 0;
|
|
||||||
info.hasCore = true;
|
info.hasCore = true;
|
||||||
info.secondsPassed = 0;
|
|
||||||
|
|
||||||
state.rules.sector.saveInfo();
|
state.rules.sector.saveInfo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
import mindustry.maps.*;
|
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||||
@@ -63,18 +62,10 @@ public class SectorInfo{
|
|||||||
public int attempts;
|
public int attempts;
|
||||||
/** Wave # from state */
|
/** Wave # from state */
|
||||||
public int wave = 1, winWave = -1;
|
public int wave = 1, winWave = -1;
|
||||||
/** Waves this sector can survive if under attack. Based on wave in info. <0 means uncalculated. */
|
|
||||||
public int wavesSurvived = -1;
|
|
||||||
/** Time between waves. */
|
/** Time between waves. */
|
||||||
public float waveSpacing = 2 * Time.toMinutes;
|
public float waveSpacing = 2 * Time.toMinutes;
|
||||||
/** Damage dealt to sector. */
|
|
||||||
public float damage;
|
|
||||||
/** How many waves have passed while the player was away. */
|
|
||||||
public int wavesPassed;
|
|
||||||
/** Packed core spawn position. */
|
/** Packed core spawn position. */
|
||||||
public int spawnPosition;
|
public int spawnPosition;
|
||||||
/** How long the player has been playing elsewhere. */
|
|
||||||
public float secondsPassed;
|
|
||||||
/** How many minutes this sector has been captured. */
|
/** How many minutes this sector has been captured. */
|
||||||
public float minutesCaptured;
|
public float minutesCaptured;
|
||||||
/** Light coverage in terms of radius. */
|
/** Light coverage in terms of radius. */
|
||||||
@@ -90,11 +81,6 @@ public class SectorInfo{
|
|||||||
/** Whether this sector was indicated to the player or not. */
|
/** Whether this sector was indicated to the player or not. */
|
||||||
public boolean shown = false;
|
public boolean shown = false;
|
||||||
|
|
||||||
/** Special variables for simulation. */
|
|
||||||
public float sumHealth, sumRps, sumDps, bossHealth, bossDps, curEnemyHealth, curEnemyDps;
|
|
||||||
/** Wave where first boss shows up. */
|
|
||||||
public int bossWave = -1;
|
|
||||||
|
|
||||||
public ObjectFloatMap<Item> importCooldownTimers = new ObjectFloatMap<>();
|
public ObjectFloatMap<Item> importCooldownTimers = new ObjectFloatMap<>();
|
||||||
public @Nullable transient float[] importRateCache;
|
public @Nullable transient float[] importRateCache;
|
||||||
|
|
||||||
@@ -233,9 +219,6 @@ public class SectorInfo{
|
|||||||
hasCore = entity != null;
|
hasCore = entity != null;
|
||||||
bestCoreType = !hasCore ? Blocks.air : state.rules.defaultTeam.cores().max(e -> e.block.size).block;
|
bestCoreType = !hasCore ? Blocks.air : state.rules.defaultTeam.cores().max(e -> e.block.size).block;
|
||||||
storageCapacity = entity != null ? entity.storageCapacity : 0;
|
storageCapacity = entity != null ? entity.storageCapacity : 0;
|
||||||
secondsPassed = 0;
|
|
||||||
wavesPassed = 0;
|
|
||||||
damage = 0;
|
|
||||||
hasSpawns = spawner.countSpawns() > 0;
|
hasSpawns = spawner.countSpawns() > 0;
|
||||||
lastPresetName = sector.preset == null ? null : sector.preset.name;
|
lastPresetName = sector.preset == null ? null : sector.preset.name;
|
||||||
lastWidth = world.width();
|
lastWidth = world.width();
|
||||||
@@ -264,10 +247,6 @@ public class SectorInfo{
|
|||||||
|
|
||||||
sector.saveInfo();
|
sector.saveInfo();
|
||||||
|
|
||||||
if(sector.planet.allowWaveSimulation){
|
|
||||||
SectorDamage.writeParameters(sector);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sector.planet.generator != null){
|
if(sector.planet.generator != null){
|
||||||
sector.planet.generator.beforeSaveWrite(sector);
|
sector.planet.generator.beforeSaveWrite(sector);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import mindustry.game.EventType.*;
|
|||||||
import mindustry.game.Schematic.*;
|
import mindustry.game.Schematic.*;
|
||||||
import mindustry.game.SectorInfo.*;
|
import mindustry.game.SectorInfo.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.maps.*;
|
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.world.blocks.storage.*;
|
import mindustry.world.blocks.storage.*;
|
||||||
|
|
||||||
@@ -158,13 +157,8 @@ public class Universe{
|
|||||||
//update relevant sectors
|
//update relevant sectors
|
||||||
for(Planet planet : content.planets()){
|
for(Planet planet : content.planets()){
|
||||||
|
|
||||||
//planets with different wave simulation status are not updated
|
//do not update other planets
|
||||||
if(current != null && current.allowWaveSimulation != planet.allowWaveSimulation){
|
if(current != null && current != planet && !current.updateGroup.contains(planet) && !planet.updateGroup.contains(current)){
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//don't simulate the planet if there is an in-progress mission on that planet
|
|
||||||
if(!planet.allowWaveSimulation && planet.sectors.contains(s -> s.hasBase() && !s.isBeingPlayed() && s.isAttacked())){
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +172,7 @@ public class Universe{
|
|||||||
|
|
||||||
//second pass: update export & import statistics
|
//second pass: update export & import statistics
|
||||||
for(Sector sector : planet.sectors){
|
for(Sector sector : planet.sectors){
|
||||||
if(sector.hasBase() && !sector.isBeingPlayed()){
|
if(sector.hasBase() && !sector.isBeingPlayed() && !sector.isAttacked()){
|
||||||
|
|
||||||
//export to another sector
|
//export to another sector
|
||||||
if(sector.info.destination != null){
|
if(sector.info.destination != null){
|
||||||
@@ -186,7 +180,7 @@ public class Universe{
|
|||||||
if(to.hasBase() && to.planet == planet){
|
if(to.hasBase() && to.planet == planet){
|
||||||
ItemSeq items = new ItemSeq();
|
ItemSeq items = new ItemSeq();
|
||||||
//calculated exported items to this sector
|
//calculated exported items to this sector
|
||||||
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * sector.getProductionScale())));
|
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed)));
|
||||||
to.addItems(items);
|
to.addItems(items);
|
||||||
to.info.lastImported.add(items);
|
to.info.lastImported.add(items);
|
||||||
}
|
}
|
||||||
@@ -209,95 +203,58 @@ public class Universe{
|
|||||||
sector.info.minutesCaptured += turnDuration / 60 / 60;
|
sector.info.minutesCaptured += turnDuration / 60 / 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
//increment seconds passed for this sector by the time that just passed with this turn
|
//attacked sectors are frozen in time; don't update those
|
||||||
if(!sector.isBeingPlayed()){
|
if(!sector.isAttacked()){
|
||||||
|
|
||||||
//TODO: if a planet has sectors under attack and simulation is OFF, just don't simulate it
|
//increment seconds passed for this sector by the time that just passed with this turn
|
||||||
|
if(!sector.isBeingPlayed()){
|
||||||
|
|
||||||
//increment time if attacked
|
//add production, making sure that it's capped
|
||||||
if(sector.isAttacked()){
|
sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * newSecondsPassed), sector.info.storageCapacity - sector.info.items.get(item))));
|
||||||
sector.info.secondsPassed += turnDuration/60f;
|
|
||||||
}
|
|
||||||
|
|
||||||
int wavesPassed = (int)(sector.info.secondsPassed*60f / sector.info.waveSpacing);
|
if(planet.campaignRules.legacyLaunchPads){
|
||||||
boolean attacked = sector.info.waves && sector.planet.allowWaveSimulation;
|
sector.info.export.each((item, stat) -> {
|
||||||
|
if(sector.info.items.get(item) <= 0 && sector.info.production.get(item, ExportStat::new).mean < 0 && stat.mean > 0){
|
||||||
if(attacked){
|
//cap export by import when production is negative.
|
||||||
sector.info.wavesPassed = wavesPassed;
|
//TODO remove
|
||||||
}
|
stat.mean = Math.min(sector.info.lastImported.get(item) / (float)newSecondsPassed, stat.mean);
|
||||||
|
}
|
||||||
float damage = attacked ? SectorDamage.getDamage(sector) : 0f;
|
});
|
||||||
|
|
||||||
//damage never goes down until the player visits the sector, so use max
|
|
||||||
sector.info.damage = Math.max(sector.info.damage, damage);
|
|
||||||
|
|
||||||
//check if the sector has been attacked too many times...
|
|
||||||
if(attacked && damage >= 0.999f){
|
|
||||||
//fire event for losing the sector
|
|
||||||
Events.fire(new SectorLoseEvent(sector));
|
|
||||||
|
|
||||||
//sector is dead.
|
|
||||||
sector.info.items.clear();
|
|
||||||
sector.info.damage = 1f;
|
|
||||||
sector.info.hasCore = false;
|
|
||||||
sector.info.production.clear();
|
|
||||||
}else if(attacked && wavesPassed > 0 && sector.info.winWave > 1 && sector.info.wave + wavesPassed >= sector.info.winWave && !sector.hasEnemyBase()){
|
|
||||||
//autocapture the sector
|
|
||||||
sector.info.waves = false;
|
|
||||||
boolean was = sector.info.wasCaptured;
|
|
||||||
sector.info.wasCaptured = true;
|
|
||||||
|
|
||||||
//fire the event
|
|
||||||
Events.fire(new SectorCaptureEvent(sector, !was));
|
|
||||||
}
|
|
||||||
|
|
||||||
float scl = sector.getProductionScale();
|
|
||||||
|
|
||||||
//add production, making sure that it's capped
|
|
||||||
sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * newSecondsPassed * scl), sector.info.storageCapacity - sector.info.items.get(item))));
|
|
||||||
|
|
||||||
if(planet.campaignRules.legacyLaunchPads){
|
|
||||||
sector.info.export.each((item, stat) -> {
|
|
||||||
if(sector.info.items.get(item) <= 0 && sector.info.production.get(item, ExportStat::new).mean < 0 && stat.mean > 0){
|
|
||||||
//cap export by import when production is negative.
|
|
||||||
//TODO remove
|
|
||||||
stat.mean = Math.min(sector.info.lastImported.get(item) / (float)newSecondsPassed, stat.mean);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//prevent negative values with unloaders
|
|
||||||
sector.info.items.checkNegative();
|
|
||||||
|
|
||||||
sector.saveInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
//queue random invasions
|
|
||||||
if(!sector.isAttacked() && sector.planet.campaignRules.sectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
|
|
||||||
int count = sector.near().count(s -> s.hasEnemyBase() && !s.hasBase() && (s.preset == null || !s.preset.requireUnlock));
|
|
||||||
|
|
||||||
//invasion chance depends on # of nearby bases
|
|
||||||
if(count > 0 && Mathf.chance(baseInvasionChance * (0.8f + (count - 1) * 0.3f))){
|
|
||||||
int waveMax = Math.max(sector.info.winWave, sector.isBeingPlayed() ? state.wave : sector.info.wave + sector.info.wavesPassed) + Mathf.random(2, 4) * 5;
|
|
||||||
|
|
||||||
//assign invasion-related things
|
|
||||||
if(sector.isBeingPlayed()){
|
|
||||||
state.rules.winWave = waveMax;
|
|
||||||
state.rules.waves = true;
|
|
||||||
state.rules.attackMode = false;
|
|
||||||
planet.campaignRules.apply(planet, state.rules); //enabling waves may force changes in campaign rules
|
|
||||||
//update rules in multiplayer
|
|
||||||
if(net.server()){
|
|
||||||
Call.setRules(state.rules);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
sector.info.winWave = waveMax;
|
|
||||||
sector.info.waves = true;
|
|
||||||
sector.info.attack = false;
|
|
||||||
sector.saveInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Events.fire(new SectorInvasionEvent(sector));
|
//prevent negative values with unloaders
|
||||||
|
sector.info.items.checkNegative();
|
||||||
|
|
||||||
|
sector.saveInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
//queue random invasions
|
||||||
|
if(sector.planet.campaignRules.sectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
|
||||||
|
int count = sector.near().count(s -> s.hasEnemyBase() && !s.hasBase() && (s.preset == null || !s.preset.requireUnlock));
|
||||||
|
|
||||||
|
//invasion chance depends on # of nearby bases
|
||||||
|
if(count > 0 && Mathf.chance(baseInvasionChance * (0.8f + (count - 1) * 0.3f))){
|
||||||
|
int waveMax = Math.max(sector.info.winWave, sector.isBeingPlayed() ? state.wave : sector.info.wave) + Mathf.random(2, 4) * 5;
|
||||||
|
|
||||||
|
//assign invasion-related things
|
||||||
|
if(sector.isBeingPlayed()){
|
||||||
|
state.rules.winWave = waveMax;
|
||||||
|
state.rules.waves = true;
|
||||||
|
state.rules.attackMode = false;
|
||||||
|
planet.campaignRules.apply(planet, state.rules); //enabling waves may force changes in campaign rules
|
||||||
|
//update rules in multiplayer
|
||||||
|
if(net.server()){
|
||||||
|
Call.setRules(state.rules);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
sector.info.winWave = waveMax;
|
||||||
|
sector.info.waves = true;
|
||||||
|
sector.info.attack = false;
|
||||||
|
sector.saveInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
Events.fire(new SectorInvasionEvent(sector));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,379 +4,17 @@ import arc.math.*;
|
|||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import mindustry.ai.*;
|
import mindustry.ai.*;
|
||||||
import mindustry.ai.types.*;
|
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.core.*;
|
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.game.*;
|
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.logic.*;
|
|
||||||
import mindustry.type.*;
|
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.defense.*;
|
|
||||||
import mindustry.world.blocks.defense.turrets.*;
|
|
||||||
import mindustry.world.blocks.defense.turrets.Turret.*;
|
|
||||||
import mindustry.world.blocks.storage.*;
|
import mindustry.world.blocks.storage.*;
|
||||||
|
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
public class SectorDamage{
|
public class SectorDamage{
|
||||||
public static final int maxRetWave = 110, maxWavesSimulated = 111;
|
|
||||||
|
|
||||||
//direct damage is for testing only
|
|
||||||
private static final boolean rubble = true;
|
private static final boolean rubble = true;
|
||||||
|
|
||||||
/** @return calculated capture progress of the enemy */
|
|
||||||
public static float getDamage(Sector sector){
|
|
||||||
return getDamage(sector, sector.info.wavesPassed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return calculated capture progress of the enemy */
|
|
||||||
public static float getDamage(Sector sector, int wavesPassed){
|
|
||||||
return getDamage(sector, wavesPassed, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return maximum waves survived, up to maxRetWave. */
|
|
||||||
public static int getWavesSurvived(Sector sector){
|
|
||||||
return (int)getDamage(sector, maxRetWave, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return calculated capture progress of the enemy if retWave is false, otherwise return the maximum waves survived as int.
|
|
||||||
* if it survives all the waves, returns maxRetWave. */
|
|
||||||
public static float getDamage(Sector sector, int wavesPassed, boolean retWave){
|
|
||||||
var info = sector.info;
|
|
||||||
float health = info.sumHealth;
|
|
||||||
int wave = info.wave;
|
|
||||||
float waveSpace = info.waveSpacing;
|
|
||||||
|
|
||||||
//this approach is O(n), it simulates every wave passing.
|
|
||||||
//other approaches can assume all the waves come as one, but that's not as fair.
|
|
||||||
if(wavesPassed > 0){
|
|
||||||
int waveBegin = wave;
|
|
||||||
int waveEnd = wave + wavesPassed;
|
|
||||||
|
|
||||||
//do not simulate every single wave if there's too many
|
|
||||||
if(wavesPassed > maxWavesSimulated && !retWave){
|
|
||||||
waveBegin = waveEnd - maxWavesSimulated;
|
|
||||||
}
|
|
||||||
|
|
||||||
int groundSpawns = Math.max(spawner.countFlyerSpawns(), 1), airSpawns = Math.max(spawner.countGroundSpawns(), 1);
|
|
||||||
|
|
||||||
for(int i = waveBegin; i <= waveEnd; i++){
|
|
||||||
float enemyDps = 0f, enemyHealth = 0f;
|
|
||||||
|
|
||||||
if(sector.save != null || sector.isBeingPlayed()){
|
|
||||||
for(SpawnGroup group : (sector.isBeingPlayed() ? state.rules.spawns : sector.save.meta.rules.spawns)){
|
|
||||||
//calculate the amount of spawn points used
|
|
||||||
//if there's a spawn position override, there is only one potential place they spawn
|
|
||||||
//assume that all overridden positions are valid, should always be true in properly designed campaign maps
|
|
||||||
int spawnCount = group.spawn != -1 ? 1 : group.type.flying ? airSpawns : groundSpawns;
|
|
||||||
|
|
||||||
float healthMult = 1f + Mathf.clamp(group.type.armor / 20f);
|
|
||||||
StatusEffect effect = (group.effect == null ? StatusEffects.none : group.effect);
|
|
||||||
int spawned = group.getSpawned(i) * spawnCount;
|
|
||||||
if(spawned <= 0) continue;
|
|
||||||
enemyHealth += spawned * (group.getShield(i) + group.type.health * effect.healthMultiplier * healthMult);
|
|
||||||
enemyDps += spawned * group.type.dpsEstimate * effect.damageMultiplier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float efficiency = health / info.sumHealth;
|
|
||||||
float dps = info.sumDps * efficiency;
|
|
||||||
float rps = info.sumRps * efficiency;
|
|
||||||
|
|
||||||
if(info.bossWave == i){
|
|
||||||
enemyDps += info.bossDps;
|
|
||||||
enemyHealth += info.bossHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i == waveBegin){
|
|
||||||
enemyDps += info.curEnemyDps;
|
|
||||||
enemyHealth += info.curEnemyHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
//happens due to certain regressions
|
|
||||||
if(enemyHealth < 0 || enemyDps < 0) continue;
|
|
||||||
|
|
||||||
//calculate time to destroy both sides
|
|
||||||
float timeDestroyEnemy = dps <= 0.0001f ? Float.POSITIVE_INFINITY : enemyHealth / dps; //if dps == 0, this is infinity
|
|
||||||
float timeDestroyBase = health / (enemyDps - rps); //if regen > enemyDps this is negative
|
|
||||||
|
|
||||||
//regenerating faster than the base can be damaged
|
|
||||||
if(timeDestroyBase < 0) continue;
|
|
||||||
|
|
||||||
//sector is lost, enemy took too long.
|
|
||||||
if(timeDestroyEnemy > timeDestroyBase){
|
|
||||||
health = 0f;
|
|
||||||
//return current wave if simulating
|
|
||||||
if(retWave) return Math.max(i - waveBegin - 1, waveBegin);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//otherwise, the enemy shoots for timeDestroyEnemy seconds, so calculate damage taken
|
|
||||||
float damageTaken = timeDestroyEnemy * (enemyDps - rps);
|
|
||||||
|
|
||||||
//damage the base.
|
|
||||||
health -= damageTaken;
|
|
||||||
|
|
||||||
//regen health after wave.
|
|
||||||
health = Math.min(health + rps / 60f * waveSpace, info.sumHealth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//survived everything
|
|
||||||
if(retWave){
|
|
||||||
return maxRetWave;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1f - Mathf.clamp(health / info.sumHealth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Applies wave damage based on sector parameters. */
|
|
||||||
public static void applyCalculatedDamage(){
|
|
||||||
//calculate base damage fraction
|
|
||||||
float damage = getDamage(state.rules.sector);
|
|
||||||
|
|
||||||
//scaled damage has a power component to make it seem a little more realistic (as systems fail, enemy capturing gets easier and easier)
|
|
||||||
float scaled = Mathf.pow(damage, 1.2f);
|
|
||||||
|
|
||||||
Tile spawn = spawner.getFirstSpawn();
|
|
||||||
|
|
||||||
//damage only units near the spawn point
|
|
||||||
if(spawn != null){
|
|
||||||
Seq<Unit> allies = new Seq<>();
|
|
||||||
float sumUnitHealth = 0f;
|
|
||||||
for(Unit ally : Groups.unit){
|
|
||||||
if(ally.team == state.rules.defaultTeam && ally.within(spawn, state.rules.dropZoneRadius * 2.5f)){
|
|
||||||
allies.add(ally);
|
|
||||||
sumUnitHealth += ally.health;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allies.sort(u -> u.dst2(spawn));
|
|
||||||
|
|
||||||
//apply damage to units
|
|
||||||
float unitDamage = damage * sumUnitHealth;
|
|
||||||
|
|
||||||
//damage units one by one, not uniformly
|
|
||||||
for(var u : allies){
|
|
||||||
if(u.health < unitDamage){
|
|
||||||
u.remove();
|
|
||||||
unitDamage -= u.health;
|
|
||||||
}else{
|
|
||||||
u.health -= unitDamage;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(state.rules.sector.info.wavesPassed > 0){
|
|
||||||
//simply remove each block in the spawner range if a wave passed
|
|
||||||
for(Tile spawner : spawner.getSpawns()){
|
|
||||||
spawner.circle((int)(state.rules.dropZoneRadius / tilesize), tile -> {
|
|
||||||
if(tile.team() == state.rules.defaultTeam){
|
|
||||||
if(rubble && tile.floor().hasSurface() && Mathf.chance(0.4)){
|
|
||||||
Effect.rubble(tile.build.x, tile.build.y, tile.block().size);
|
|
||||||
}
|
|
||||||
|
|
||||||
tile.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//finally apply scaled damage
|
|
||||||
apply(scaled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calculates damage simulation parameters before a game is saved. */
|
|
||||||
public static void writeParameters(Sector sector){
|
|
||||||
var info = sector.info;
|
|
||||||
Building core = state.rules.defaultTeam.core();
|
|
||||||
Seq<Tile> spawns = new Seq<>();
|
|
||||||
spawner.eachGroundSpawn((x, y) -> spawns.add(world.tile(x, y)));
|
|
||||||
|
|
||||||
if(spawns.isEmpty() && state.rules.waveTeam.core() != null){
|
|
||||||
spawns.add(state.rules.waveTeam.core().tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(core == null || spawns.isEmpty()) return;
|
|
||||||
|
|
||||||
boolean airOnly = !state.rules.spawns.contains(g -> !g.type.flying);
|
|
||||||
|
|
||||||
Tile start = spawns.first();
|
|
||||||
Seq<Tile> path = new Seq<>();
|
|
||||||
|
|
||||||
//TODO would be nice if this worked in a more generic way, with two different calculations and paths
|
|
||||||
if(airOnly){
|
|
||||||
World.raycastEach(start.x, start.y, core.tileX(), core.tileY(), (x, y) -> {
|
|
||||||
path.add(world.rawTile(x, y));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
var field = pathfinder.getField(state.rules.waveTeam, Pathfinder.costGround, Pathfinder.fieldCore);
|
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
if(field != null && field.weights != null){
|
|
||||||
int[] weights = field.weights;
|
|
||||||
int count = 0;
|
|
||||||
Tile current = start;
|
|
||||||
while(count < weights.length){
|
|
||||||
int minCost = Integer.MAX_VALUE;
|
|
||||||
int cx = current.x, cy = current.y;
|
|
||||||
for(Point2 p : Geometry.d4){
|
|
||||||
int nx = cx + p.x, ny = cy + p.y, packed = world.packArray(nx, ny);
|
|
||||||
|
|
||||||
Tile other = world.tile(nx, ny);
|
|
||||||
if(other != null && weights[packed] < minCost && weights[packed] != -1){
|
|
||||||
minCost = weights[packed];
|
|
||||||
current = other;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path.add(current);
|
|
||||||
|
|
||||||
if(current.build == core){
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
count ++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!found){
|
|
||||||
path.clear();
|
|
||||||
path.addAll(Astar.pathfind(start, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//create sparse tile array for fast range query
|
|
||||||
int sparseSkip = 5, sparseSkip2 = 3;
|
|
||||||
Seq<Tile> sparse = new Seq<>(path.size / sparseSkip + 1);
|
|
||||||
Seq<Tile> sparse2 = new Seq<>(path.size / sparseSkip2 + 1);
|
|
||||||
|
|
||||||
for(int i = 0; i < path.size; i++){
|
|
||||||
if(i % sparseSkip == 0){
|
|
||||||
sparse.add(path.get(i));
|
|
||||||
}
|
|
||||||
if(i % sparseSkip2 == 0){
|
|
||||||
sparse2.add(path.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//regen is in health per second
|
|
||||||
//dps is per second
|
|
||||||
float sumHealth = 0f, sumRps = 0f, sumDps = 0f;
|
|
||||||
float totalPathBuild = 0;
|
|
||||||
|
|
||||||
//first, calculate the total health of blocks in the path
|
|
||||||
|
|
||||||
//radius around the path that gets counted
|
|
||||||
int radius = 6;
|
|
||||||
IntSet counted = new IntSet();
|
|
||||||
|
|
||||||
for(Tile t : sparse2){
|
|
||||||
|
|
||||||
//radius is square.
|
|
||||||
for(int dx = -radius; dx <= radius; dx++){
|
|
||||||
for(int dy = -radius; dy <= radius; dy++){
|
|
||||||
int wx = dx + t.x, wy = dy + t.y;
|
|
||||||
if(wx >= 0 && wy >= 0 && wx < world.width() && wy < world.height()){
|
|
||||||
Tile tile = world.rawTile(wx, wy);
|
|
||||||
|
|
||||||
if(tile.build != null && tile.team() == state.rules.defaultTeam && counted.add(tile.pos())){
|
|
||||||
//health is divided by block size, because multiblocks are counted multiple times.
|
|
||||||
sumHealth += tile.build.health / (tile.block().size * tile.block().size);
|
|
||||||
totalPathBuild += 1f / (tile.block().size * tile.block().size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float avgHealth = totalPathBuild <= 1 ? sumHealth : sumHealth / totalPathBuild;
|
|
||||||
|
|
||||||
//block dps + regen + extra health/shields
|
|
||||||
for(Building build : state.rules.defaultTeam.data().buildings){
|
|
||||||
float e = build.potentialEfficiency;
|
|
||||||
if(e > 0.08f){
|
|
||||||
if(build instanceof Ranged ranged && sparse.contains(t -> t.within(build, ranged.range() + 4*tilesize))){
|
|
||||||
//TODO make sure power turret network supports the turrets?
|
|
||||||
if(build instanceof TurretBuild b && b.hasAmmo()){
|
|
||||||
sumDps += b.estimateDps();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(build.block instanceof MendProjector m){
|
|
||||||
sumRps += m.healPercent / m.reload * avgHealth * 60f / 100f * e * build.timeScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
//point defense turrets act as flat health right now
|
|
||||||
if(build.block instanceof PointDefenseTurret){
|
|
||||||
sumHealth += 150f * build.timeScale() * build.potentialEfficiency;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(build.block instanceof ForceProjector f){
|
|
||||||
sumHealth += f.shieldHealth * e * build.timeScale();
|
|
||||||
sumRps += e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float curEnemyHealth = 0f, curEnemyDps = 0f;
|
|
||||||
|
|
||||||
//unit regen + health + dps
|
|
||||||
for(Unit unit : Groups.unit){
|
|
||||||
//skip player
|
|
||||||
if(unit.isPlayer()) continue;
|
|
||||||
|
|
||||||
//scale health based on armor - yes, this is inaccurate, but better than nothing
|
|
||||||
float healthMult = 1f + Mathf.clamp(unit.armor / 20f);
|
|
||||||
|
|
||||||
if(unit.team == state.rules.defaultTeam){
|
|
||||||
sumHealth += unit.health*healthMult + unit.shield;
|
|
||||||
sumDps += unit.type.dpsEstimate;
|
|
||||||
sumRps += unit.type.weapons.sumf(w -> w.shotsPerSec() * (w.bullet.healPercent/100f * 20f + w.bullet.healAmount));
|
|
||||||
if(unit.controller() instanceof CommandAI ai && ai.command == UnitCommand.rebuildCommand){
|
|
||||||
sumRps += unit.type.buildSpeed * 20f;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
float bossMult = unit.isBoss() ? 3f : 1f;
|
|
||||||
curEnemyDps += unit.type.dpsEstimate * unit.damageMultiplier() * bossMult;
|
|
||||||
curEnemyHealth += unit.health * healthMult * unit.healthMultiplier() * bossMult + unit.shield;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SpawnGroup bossGroup = state.rules.spawns.find(s -> s.effect == StatusEffects.boss);
|
|
||||||
|
|
||||||
if(bossGroup != null){
|
|
||||||
float bossMult = 1.2f;
|
|
||||||
//calculate first boss appearance
|
|
||||||
for(int wave = state.wave; wave < state.wave + 60; wave++){
|
|
||||||
int spawned = bossGroup.getSpawned(wave - 1);
|
|
||||||
if(spawned > 0){
|
|
||||||
//set up relevant stats
|
|
||||||
info.bossWave = wave;
|
|
||||||
info.bossDps = spawned * bossGroup.type.dpsEstimate * StatusEffects.boss.damageMultiplier * bossMult;
|
|
||||||
info.bossHealth = spawned * (bossGroup.getShield(wave) + bossGroup.type.health * StatusEffects.boss.healthMultiplier * (1f + Mathf.clamp(bossGroup.type.armor / 20f))) * bossMult;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info.sumHealth = sumHealth * 0.9f;
|
|
||||||
info.sumDps = sumDps;
|
|
||||||
info.sumRps = sumRps;
|
|
||||||
|
|
||||||
float cmult = 1.6f;
|
|
||||||
|
|
||||||
info.curEnemyDps = curEnemyDps*cmult;
|
|
||||||
info.curEnemyHealth = curEnemyHealth*cmult;
|
|
||||||
|
|
||||||
info.wavesSurvived = getWavesSurvived(sector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void apply(float fraction){
|
public static void apply(float fraction){
|
||||||
Tiles tiles = world.tiles;
|
Tiles tiles = world.tiles;
|
||||||
|
|
||||||
|
|||||||
@@ -115,8 +115,6 @@ public class Planet extends UnlockableContent{
|
|||||||
public boolean allowLaunchSchematics = false;
|
public boolean allowLaunchSchematics = false;
|
||||||
/** Whether to allow users to specify the resources they take to this map. */
|
/** Whether to allow users to specify the resources they take to this map. */
|
||||||
public boolean allowLaunchLoadout = false;
|
public boolean allowLaunchLoadout = false;
|
||||||
/** Whether to allow sectors to simulate waves in the background. */
|
|
||||||
public boolean allowWaveSimulation = false;
|
|
||||||
/** Whether to simulate sector invasions from enemy bases. */
|
/** Whether to simulate sector invasions from enemy bases. */
|
||||||
public boolean allowSectorInvasion = false;
|
public boolean allowSectorInvasion = false;
|
||||||
/** If true, legacy launch pads can be enabled. */
|
/** If true, legacy launch pads can be enabled. */
|
||||||
@@ -164,6 +162,8 @@ public class Planet extends UnlockableContent{
|
|||||||
/** Loads the planet grid outline mesh. Clientside only. */
|
/** Loads the planet grid outline mesh. Clientside only. */
|
||||||
public Prov<Mesh> gridMeshLoader = () -> MeshBuilder.buildPlanetGrid(grid, outlineColor, outlineRad * radius);
|
public Prov<Mesh> gridMeshLoader = () -> MeshBuilder.buildPlanetGrid(grid, outlineColor, outlineRad * radius);
|
||||||
|
|
||||||
|
/** Planets that are allowed to update at the same time as this one for background calculations. */
|
||||||
|
public ObjectSet<Planet> updateGroup = new ObjectSet<>();
|
||||||
/** Global difficulty/modifier settings for this planet's campaign. */
|
/** Global difficulty/modifier settings for this planet's campaign. */
|
||||||
public CampaignRules campaignRules = new CampaignRules();
|
public CampaignRules campaignRules = new CampaignRules();
|
||||||
/** Defaults applied to the rules. */
|
/** Defaults applied to the rules. */
|
||||||
@@ -251,7 +251,6 @@ public class Planet extends UnlockableContent{
|
|||||||
|
|
||||||
public void applyDefaultRules(CampaignRules rules){
|
public void applyDefaultRules(CampaignRules rules){
|
||||||
JsonIO.copy(campaignRuleDefaults, rules);
|
JsonIO.copy(campaignRuleDefaults, rules);
|
||||||
rules.sectorInvasion = allowSectorInvasion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Sector getLastSector(){
|
public @Nullable Sector getLastSector(){
|
||||||
|
|||||||
@@ -121,10 +121,6 @@ public class Sector{
|
|||||||
Core.settings.remove(planet.name + "-s-" + id + "-info");
|
Core.settings.remove(planet.name + "-s-" + id + "-info");
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getProductionScale(){
|
|
||||||
return Math.max(1f - info.damage, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAttacked(){
|
public boolean isAttacked(){
|
||||||
if(isBeingPlayed()) return state.rules.waves || state.rules.attackMode;
|
if(isBeingPlayed()) return state.rules.waves || state.rules.attackMode;
|
||||||
return save != null && (info.waves || info.attack) && info.hasCore;
|
return save != null && (info.waves || info.attack) && info.hasCore;
|
||||||
@@ -135,6 +131,10 @@ public class Sector{
|
|||||||
return save != null && info.hasCore && !(Vars.state.isGame() && Vars.state.rules.sector == this && state.gameOver);
|
return save != null && info.hasCore && !(Vars.state.isGame() && Vars.state.rules.sector == this && state.gameOver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFrozen(){
|
||||||
|
return isAttacked() && !isBeingPlayed();
|
||||||
|
}
|
||||||
|
|
||||||
/** @return whether the enemy has a generated base here. */
|
/** @return whether the enemy has a generated base here. */
|
||||||
public boolean hasEnemyBase(){
|
public boolean hasEnemyBase(){
|
||||||
return ((generateEnemyBase && preset == null) || (preset != null && preset.captureWave == 0)) && (save == null || info.attack);
|
return ((generateEnemyBase && preset == null) || (preset != null && preset.captureWave == 0)) && (save == null || info.attack);
|
||||||
|
|||||||
@@ -587,8 +587,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean selectable(Planet planet){
|
boolean selectable(Planet planet){
|
||||||
//TODO what if any sector is selectable?
|
|
||||||
//TODO launch criteria - which planets can be launched to? Where should this be defined? Should planets even be selectable?
|
|
||||||
if(mode == select) return planet == state.planet;
|
if(mode == select) return planet == state.planet;
|
||||||
if(mode == planetLaunch) return launchSector != null && (launchCandidates.contains(planet) || (planet == launchSector.planet && planet.allowSelfSectorLaunch));
|
if(mode == planetLaunch) return launchSector != null && (launchCandidates.contains(planet) || (planet == launchSector.planet && planet.allowSelfSectorLaunch));
|
||||||
return (planet.alwaysUnlocked && planet.isLandable()) || planet.sectors.contains(Sector::hasBase) || debugSelect;
|
return (planet.alwaysUnlocked && planet.isLandable()) || planet.sectors.contains(Sector::hasBase) || debugSelect;
|
||||||
@@ -1006,18 +1004,18 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
state.uiAlpha = Mathf.lerpDelta(state.uiAlpha, Mathf.num(state.zoom < 1.9f), 0.1f);
|
state.uiAlpha = Mathf.lerpDelta(state.uiAlpha, Mathf.num(state.zoom < 1.9f), 0.1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void displayItems(Table c, float scl, ObjectMap<Item, ExportStat> stats, String name){
|
void displayItems(Table c, ObjectMap<Item, ExportStat> stats, String name){
|
||||||
displayItems(c, scl, stats, name, t -> {});
|
displayItems(c, stats, name, t -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void displayItems(Table c, float scl, ObjectMap<Item, ExportStat> stats, String name, Cons<Table> builder){
|
void displayItems(Table c, ObjectMap<Item, ExportStat> stats, String name, Cons<Table> builder){
|
||||||
Table t = new Table().left();
|
Table t = new Table().left();
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(var item : content.items()){
|
for(var item : content.items()){
|
||||||
var stat = stats.get(item);
|
var stat = stats.get(item);
|
||||||
if(stat == null) continue;
|
if(stat == null) continue;
|
||||||
int total = (int)(stat.mean * 60 * scl);
|
int total = (int)(stat.mean * 60);
|
||||||
if(total > 1){
|
if(total > 1){
|
||||||
t.image(item.uiIcon).padRight(3);
|
t.image(item.uiIcon).padRight(3);
|
||||||
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray).padRight(3);
|
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray).padRight(3);
|
||||||
@@ -1052,7 +1050,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(sector.info.waves && sector.hasBase()){
|
if(sector.info.waves && sector.hasBase()){
|
||||||
c.add(Core.bundle.get("sectors.wave") + " [accent]" + (sector.info.wave + sector.info.wavesPassed)).left().row();
|
c.add(Core.bundle.get("sectors.wave") + " [accent]" + sector.info.wave).left().row();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sector.isAttacked() || !sector.hasBase()){
|
if(sector.isAttacked() || !sector.hasBase()){
|
||||||
@@ -1069,11 +1067,17 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
}).padLeft(10f).left().row();
|
}).padLeft(10f).left().row();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean lockdown = sector.isAttacked() && !sector.isBeingPlayed();
|
||||||
|
|
||||||
|
if(lockdown){
|
||||||
|
c.add(UI.formatIcons(bundle.get("sector.lockdown"))).wrap().fillX().padBottom(10f).row();
|
||||||
|
}
|
||||||
|
|
||||||
//production
|
//production
|
||||||
displayItems(c, sector.getProductionScale(), sector.info.production, "@sectors.production");
|
displayItems(c, sector.info.production, "@sectors.production");
|
||||||
|
|
||||||
//export
|
//export
|
||||||
displayItems(c, sector.getProductionScale(), sector.info.export, "@sectors.export", t -> {
|
displayItems(c, sector.info.export, "@sectors.export", t -> {
|
||||||
if(sector.info.destination != null && sector.info.destination.hasBase()){
|
if(sector.info.destination != null && sector.info.destination.hasBase()){
|
||||||
String ic = sector.info.destination.iconChar();
|
String ic = sector.info.destination.iconChar();
|
||||||
t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + sector.info.destination.name()).padLeft(10f).row();
|
t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + sector.info.destination.name()).padLeft(10f).row();
|
||||||
@@ -1082,7 +1086,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
|
|
||||||
//import
|
//import
|
||||||
if(sector.hasBase()){
|
if(sector.hasBase()){
|
||||||
displayItems(c, 1f, sector.info.imports, "@sectors.import", t -> {
|
displayItems(c, sector.info.imports, "@sectors.import", t -> {
|
||||||
sector.info.eachImport(sector.planet, other -> {
|
sector.info.eachImport(sector.planet, other -> {
|
||||||
String ic = other.iconChar();
|
String ic = other.iconChar();
|
||||||
t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + other.name()).padLeft(10f).row();
|
t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + other.name()).padLeft(10f).row();
|
||||||
@@ -1138,7 +1142,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
sector.info.items.clear();
|
sector.info.items.clear();
|
||||||
sector.info.damage = 1f;
|
|
||||||
sector.info.hasCore = false;
|
sector.info.hasCore = false;
|
||||||
sector.info.production.clear();
|
sector.info.production.clear();
|
||||||
sector.saveInfo();
|
sector.saveInfo();
|
||||||
@@ -1150,14 +1153,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
|
|
||||||
void addSurvivedInfo(Sector sector, Table table, boolean wrap){
|
void addSurvivedInfo(Sector sector, Table table, boolean wrap){
|
||||||
if(!wrap){
|
if(!wrap){
|
||||||
table.add(sector.planet.allowWaveSimulation ? Core.bundle.format("sectors.underattack", (int)(sector.info.damage * 100)) : "@sectors.underattack.nodamage").wrapLabel(wrap).row();
|
table.add("@sectors.underattack").wrapLabel(wrap).row();
|
||||||
}
|
|
||||||
|
|
||||||
if(sector.planet.allowWaveSimulation && sector.info.wavesSurvived >= 0 && sector.info.wavesSurvived - sector.info.wavesPassed >= 0 && !sector.isBeingPlayed()){
|
|
||||||
int toCapture = sector.info.attack || sector.info.winWave <= 1 ? -1 : sector.info.winWave - (sector.info.wave + sector.info.wavesPassed);
|
|
||||||
boolean plus = (sector.info.wavesSurvived - sector.info.wavesPassed) >= SectorDamage.maxRetWave - 1;
|
|
||||||
table.add(Core.bundle.format("sectors.survives", Math.min(sector.info.wavesSurvived - sector.info.wavesPassed, toCapture <= 0 ? 200 : toCapture) +
|
|
||||||
(plus ? "+" : "") + (toCapture < 0 ? "" : "/" + toCapture))).wrapLabel(wrap).row();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1349,27 +1345,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Planet planet = sector.planet;
|
|
||||||
|
|
||||||
//make sure there are no under-attack sectors (other than this one)
|
|
||||||
if(!planet.allowWaveSimulation && !debugSelect){
|
|
||||||
//if there are two or more attacked sectors... something went wrong, don't show the dialog to prevent softlock
|
|
||||||
Sector attacked = planet.sectors.find(s -> s.isAttacked() && s != sector);
|
|
||||||
if(attacked != null && planet.sectors.count(s -> s.isAttacked()) < 2){
|
|
||||||
BaseDialog dialog = new BaseDialog("@sector.noswitch.title");
|
|
||||||
dialog.cont.add(bundle.format("sector.noswitch", attacked.name(), attacked.planet.localizedName)).width(400f).labelAlign(Align.center).center().wrap();
|
|
||||||
dialog.addCloseButton();
|
|
||||||
dialog.buttons.button("@sector.view", Icon.eyeSmall, () -> {
|
|
||||||
dialog.hide();
|
|
||||||
lookAt(attacked);
|
|
||||||
selectSector(attacked);
|
|
||||||
});
|
|
||||||
dialog.show();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean shouldHide = true;
|
boolean shouldHide = true;
|
||||||
|
|
||||||
//save before launch.
|
//save before launch.
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ public class ResearchDialog extends BaseDialog{
|
|||||||
//add global counts of each sector
|
//add global counts of each sector
|
||||||
for(Planet planet : rootPlanets){
|
for(Planet planet : rootPlanets){
|
||||||
for(Sector sector : planet.sectors){
|
for(Sector sector : planet.sectors){
|
||||||
if(sector.hasBase()){
|
if(sector.hasBase() && !sector.isFrozen()){
|
||||||
ItemSeq cached = sector.items();
|
ItemSeq cached = sector.items();
|
||||||
cache.put(sector, cached);
|
cache.put(sector, cached);
|
||||||
cached.each((item, amount) -> {
|
cached.each((item, amount) -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user