mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-01-25 22:12:16 -08:00
Sector refactoring, invasions and cleanup
This commit is contained in:
parent
5ee4101ba4
commit
2f54edf34f
27 changed files with 319 additions and 376 deletions
|
|
@ -285,6 +285,7 @@ selectschematic = [accent][[{0}][] to select+copy
|
|||
pausebuilding = [accent][[{0}][] to pause building
|
||||
resumebuilding = [scarlet][[{0}][] to resume building
|
||||
wave = [accent]Wave {0}
|
||||
wave.cap = [accent]Wave {0}/{1}
|
||||
wave.waiting = [lightgray]Wave in {0}
|
||||
wave.waveInProgress = [lightgray]Wave in progress
|
||||
waiting = [lightgray]Waiting...
|
||||
|
|
@ -521,6 +522,7 @@ sectors.resume = Resume
|
|||
sectors.launch = Launch
|
||||
sectors.select = Select
|
||||
sectors.nonelaunch = [lightgray]none (sun)
|
||||
sectors.rename = Rename Sector
|
||||
|
||||
planet.serpulo.name = Serpulo
|
||||
#TODO better name
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ public class Vars implements Loadable{
|
|||
public static final float logicItemTransferRange = 45f;
|
||||
/** duration of time between turns in ticks */
|
||||
public static final float turnDuration = 2 * Time.toMinutes;
|
||||
/** chance of an invasion per turn, 1 = 100% */
|
||||
public static final float baseInvasionChance = 1f / 15f;
|
||||
/** how many turns have to pass before invasions start */
|
||||
public static final int invasionGracePeriod = 20;
|
||||
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||
public static final float minArmorDamage = 0.1f;
|
||||
/** launch animation duration */
|
||||
|
|
|
|||
|
|
@ -279,6 +279,7 @@ public class Control implements ApplicationListener, Loadable{
|
|||
slot.load();
|
||||
slot.setAutosave(true);
|
||||
state.rules.sector = sector;
|
||||
state.secinfo = state.rules.sector.info;
|
||||
|
||||
//if there is no base, simulate a new game and place the right loadout at the spawn position
|
||||
if(state.rules.defaultTeam.cores().isEmpty()){
|
||||
|
|
@ -286,11 +287,9 @@ public class Control implements ApplicationListener, Loadable{
|
|||
state.wave = 1;
|
||||
|
||||
//kill all units, since they should be dead anwyay
|
||||
for(Unit unit : Groups.unit){
|
||||
unit.remove();
|
||||
}
|
||||
Groups.unit.clear();
|
||||
|
||||
Tile spawn = world.tile(sector.getSpawnPosition());
|
||||
Tile spawn = world.tile(sector.info.spawnPosition);
|
||||
Schematics.placeLoadout(universe.getLastLoadout(), spawn.x, spawn.y);
|
||||
|
||||
//set up camera/player locations
|
||||
|
|
@ -313,7 +312,6 @@ public class Control implements ApplicationListener, Loadable{
|
|||
}else{
|
||||
net.reset();
|
||||
logic.reset();
|
||||
sector.setSecondsPassed(0);
|
||||
world.loadSector(sector);
|
||||
state.rules.sector = sector;
|
||||
//assign origin when launching
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import mindustry.type.Weather.*;
|
|||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
|
@ -88,13 +87,10 @@ public class Logic implements ApplicationListener{
|
|||
//when loading a 'damaged' sector, propagate the damage
|
||||
Events.on(SaveLoadEvent.class, e -> {
|
||||
if(state.isCampaign()){
|
||||
CoreBuild core = state.rules.defaultTeam.core();
|
||||
state.secinfo.write();
|
||||
|
||||
//how much wave time has passed
|
||||
int wavesPassed = state.rules.sector.getWavesPassed();
|
||||
|
||||
//reset passed waves
|
||||
state.rules.sector.setWavesPassed(0);
|
||||
int wavesPassed = state.secinfo.wavesPassed;
|
||||
|
||||
//wave has passed, remove all enemies, they are assumed to be dead
|
||||
if(wavesPassed > 0){
|
||||
|
|
@ -105,44 +101,22 @@ public class Logic implements ApplicationListener{
|
|||
});
|
||||
}
|
||||
|
||||
//simulate passing of waves
|
||||
if(wavesPassed > 0){
|
||||
//simulate wave counter moving forward
|
||||
state.wave += wavesPassed;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
|
||||
SectorDamage.applyCalculatedDamage();
|
||||
}
|
||||
|
||||
//reset damage display
|
||||
state.rules.sector.setDamage(0f);
|
||||
//reset values
|
||||
state.secinfo.damage = 0f;
|
||||
state.secinfo.wavesPassed = 0;
|
||||
state.secinfo.hasCore = true;
|
||||
state.secinfo.secondsPassed = 0;
|
||||
|
||||
//simulate damage if applicable
|
||||
if(wavesPassed > 0){
|
||||
SectorDamage.applyCalculatedDamage(wavesPassed);
|
||||
}
|
||||
|
||||
//waves depend on attack status.
|
||||
state.rules.waves = state.rules.sector.isUnderAttack() || !state.rules.sector.hasBase();
|
||||
|
||||
//add resources based on turns passed
|
||||
if(state.rules.sector.save != null && core != null){
|
||||
//update correct storage capacity
|
||||
state.rules.sector.save.meta.secinfo.storageCapacity = core.storageCapacity;
|
||||
|
||||
//add new items received
|
||||
state.rules.sector.calculateReceivedItems().each((item, amount) -> core.items.add(item, amount));
|
||||
|
||||
//clear received items
|
||||
state.rules.sector.setExtraItems(new ItemSeq());
|
||||
|
||||
//validation
|
||||
for(Item item : content.items()){
|
||||
//ensure positive items
|
||||
if(core.items.get(item) < 0) core.items.set(item, 0);
|
||||
//cap the items
|
||||
if(core.items.get(item) > core.storageCapacity) core.items.set(item, core.storageCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
state.rules.sector.setSecondsPassed(0);
|
||||
state.rules.sector.saveInfo();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -200,11 +174,6 @@ public class Logic implements ApplicationListener{
|
|||
}
|
||||
|
||||
public void skipWave(){
|
||||
if(state.isCampaign()){
|
||||
//warp time spent forward because the wave was just skipped.
|
||||
state.secinfo.internalTimeSpent += state.wavetime;
|
||||
}
|
||||
|
||||
state.wavetime = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ public class World{
|
|||
setSectorRules(sector);
|
||||
|
||||
if(state.rules.defaultTeam.core() != null){
|
||||
sector.setSpawnPosition(state.rules.defaultTeam.core().pos());
|
||||
sector.info.spawnPosition = state.rules.defaultTeam.core().pos();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,8 +267,6 @@ public class World{
|
|||
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
|
||||
ObjectSet<UnlockableContent> content = new ObjectSet<>();
|
||||
|
||||
float waterFloors = 0, totalFloors = 0;
|
||||
|
||||
for(Tile tile : world.tiles){
|
||||
if(world.getDarkness(tile.x, tile.y) >= 3){
|
||||
continue;
|
||||
|
|
@ -280,10 +278,6 @@ public class World{
|
|||
if(liquid != null) content.add(liquid);
|
||||
|
||||
if(!tile.block().isStatic()){
|
||||
totalFloors ++;
|
||||
if(liquid == Liquids.water){
|
||||
waterFloors += tile.floor().isDeep() ? 1f : 0.7f;
|
||||
}
|
||||
floorc.increment(tile.floor());
|
||||
if(tile.overlay() != Blocks.air){
|
||||
floorc.increment(tile.overlay());
|
||||
|
|
@ -326,9 +320,9 @@ public class World{
|
|||
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
|
||||
}
|
||||
|
||||
state.secinfo.resources = content.asArray();
|
||||
state.secinfo.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||
|
||||
sector.info.resources = content.asArray();
|
||||
sector.info.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||
sector.saveInfo();
|
||||
}
|
||||
|
||||
public Context filterContext(Map map){
|
||||
|
|
|
|||
|
|
@ -73,6 +73,15 @@ public class EventType{
|
|||
}
|
||||
}
|
||||
|
||||
/** Called when a sector is destroyed by waves when you're not there. */
|
||||
public static class SectorInvasionEvent{
|
||||
public final Sector sector;
|
||||
|
||||
public SectorInvasionEvent(Sector sector){
|
||||
this.sector = sector;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LaunchItemEvent{
|
||||
public final ItemStack stack;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class SectorInfo{
|
|||
/** Export statistics. */
|
||||
public ObjectMap<Item, ExportStat> export = new ObjectMap<>();
|
||||
/** Items stored in all cores. */
|
||||
public ItemSeq coreItems = new ItemSeq();
|
||||
public ItemSeq items = new ItemSeq();
|
||||
/** The best available core type. */
|
||||
public Block bestCoreType = Blocks.air;
|
||||
/** Max storage capacity. */
|
||||
|
|
@ -39,13 +39,26 @@ public class SectorInfo{
|
|||
public @Nullable Sector destination;
|
||||
/** Resources known to occur at this sector. */
|
||||
public Seq<UnlockableContent> resources = new Seq<>();
|
||||
/** Whether waves are enabled here. */
|
||||
public boolean waves = true;
|
||||
/** Wave # from state */
|
||||
public int wave = 1, winWave = -1;
|
||||
/** Time between waves. */
|
||||
public float waveSpacing = 60 * 60 * 2;
|
||||
/** Damage dealt to sector. */
|
||||
public float damage;
|
||||
/** How many waves have passed while the player was away. */
|
||||
public int wavesPassed;
|
||||
/** Packed core spawn position. */
|
||||
public int spawnPosition;
|
||||
/** How long the player has been playing elsewhere. */
|
||||
public float secondsPassed;
|
||||
/** Display name. */
|
||||
public @Nullable String name;
|
||||
|
||||
/** Special variables for simulation. */
|
||||
public float sumHealth, sumRps, sumDps, waveHealthBase, waveHealthSlope, waveDpsBase, waveDpsSlope;
|
||||
|
||||
/** Time spent at this sector. Do not use unless you know what you're doing. */
|
||||
public transient float internalTimeSpent;
|
||||
|
||||
/** Counter refresh state. */
|
||||
private transient Interval time = new Interval();
|
||||
/** Core item storage to prevent spoofing. */
|
||||
|
|
@ -84,27 +97,55 @@ public class SectorInfo{
|
|||
return export.get(item, ExportStat::new).mean;
|
||||
}
|
||||
|
||||
/** Write contents of meta into main storage. */
|
||||
public void write(){
|
||||
state.wave = wave;
|
||||
state.rules.waves = waves;
|
||||
state.rules.waveSpacing = waveSpacing;
|
||||
state.rules.winWave = winWave;
|
||||
|
||||
CoreBuild entity = state.rules.defaultTeam.core();
|
||||
if(entity != null){
|
||||
entity.items.clear();
|
||||
entity.items.add(items);
|
||||
//ensure capacity.
|
||||
entity.items.each((i, a) -> entity.items.set(i, Math.min(a, entity.block.itemCapacity)));
|
||||
}
|
||||
|
||||
//TODO write items.
|
||||
}
|
||||
|
||||
/** Prepare data for writing to a save. */
|
||||
public void prepare(){
|
||||
//update core items
|
||||
coreItems.clear();
|
||||
items.clear();
|
||||
|
||||
CoreBuild entity = state.rules.defaultTeam.core();
|
||||
|
||||
if(entity != null){
|
||||
ItemModule items = entity.items;
|
||||
for(int i = 0; i < items.length(); i++){
|
||||
coreItems.set(content.item(i), items.get(i));
|
||||
this.items.set(content.item(i), items.get(i));
|
||||
}
|
||||
|
||||
spawnPosition = entity.pos();
|
||||
}
|
||||
|
||||
waveSpacing = state.rules.waveSpacing;
|
||||
wave = state.wave;
|
||||
winWave = state.rules.winWave;
|
||||
waves = state.rules.waves;
|
||||
hasCore = entity != null;
|
||||
bestCoreType = !hasCore ? Blocks.air : state.rules.defaultTeam.cores().max(e -> e.block.size).block;
|
||||
storageCapacity = entity != null ? entity.storageCapacity : 0;
|
||||
secondsPassed = 0;
|
||||
wavesPassed = 0;
|
||||
damage = 0;
|
||||
|
||||
//update sector's internal time spent counter
|
||||
state.rules.sector.setTimeSpent(internalTimeSpent);
|
||||
state.rules.sector.setUnderAttack(state.rules.waves);
|
||||
if(state.rules.sector != null){
|
||||
state.rules.sector.info = this;
|
||||
state.rules.sector.saveInfo();
|
||||
}
|
||||
|
||||
SectorDamage.writeParameters(this);
|
||||
}
|
||||
|
|
@ -115,14 +156,6 @@ public class SectorInfo{
|
|||
//updating in multiplayer as a client doesn't make sense
|
||||
if(net.client()) return;
|
||||
|
||||
internalTimeSpent += Time.delta;
|
||||
|
||||
//autorun turns
|
||||
if(internalTimeSpent >= turnDuration){
|
||||
internalTimeSpent = 0;
|
||||
universe.runTurn();
|
||||
}
|
||||
|
||||
CoreBuild ent = state.rules.defaultTeam.core();
|
||||
|
||||
//refresh throughput
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class Stats{
|
|||
|
||||
//weigh used fractions
|
||||
float frac = 0f;
|
||||
Seq<Item> obtainable = zone.save == null ? new Seq<>() : zone.save.meta.secinfo.resources.select(i -> i instanceof Item).as();
|
||||
Seq<Item> obtainable = zone.save == null ? new Seq<>() : zone.info.resources.select(i -> i instanceof Item).as();
|
||||
for(Item item : obtainable){
|
||||
frac += Mathf.clamp((float)itemsDelivered.get(item, 0) / capacity) / (float)obtainable.size;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class Universe{
|
|||
private int netSeconds;
|
||||
private float secondCounter;
|
||||
private int turn;
|
||||
private float turnCounter;
|
||||
|
||||
private Schematic lastLoadout;
|
||||
private ItemSeq lastLaunchResources = new ItemSeq();
|
||||
|
|
@ -54,17 +55,19 @@ public class Universe{
|
|||
}
|
||||
}
|
||||
|
||||
/** @return sectors attacked on the current planet, minus the ones that are being played on right now. */
|
||||
public Seq<Sector> getAttacked(Planet planet){
|
||||
return planet.sectors.select(s -> s.isUnderAttack() && s.hasBase() && !s.isBeingPlayed() && s.getWavesPassed() > 0);
|
||||
}
|
||||
|
||||
/** Update planet rotations, global time and relevant state. */
|
||||
public void update(){
|
||||
|
||||
//only update time when not in multiplayer
|
||||
if(!net.client()){
|
||||
secondCounter += Time.delta / 60f;
|
||||
turnCounter += Time.delta;
|
||||
|
||||
//auto-run turns
|
||||
if(turnCounter >= turnDuration){
|
||||
turnCounter = 0;
|
||||
runTurn();
|
||||
}
|
||||
|
||||
if(secondCounter >= 1){
|
||||
seconds += (int)secondCounter;
|
||||
|
|
@ -133,59 +136,84 @@ public class Universe{
|
|||
//update relevant sectors
|
||||
for(Planet planet : content.planets()){
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasSave()){
|
||||
int spent = (int)(sector.getTimeSpent() / 60);
|
||||
int actuallyPassed = Math.max(newSecondsPassed - spent, 0);
|
||||
if(sector.hasSave() && sector.hasBase()){
|
||||
|
||||
//increment seconds passed for this sector by the time that just passed with this turn
|
||||
if(!sector.isBeingPlayed()){
|
||||
int secPassed = sector.getSecondsPassed() + actuallyPassed;
|
||||
//increment time
|
||||
sector.info.secondsPassed += turnDuration/60f;
|
||||
|
||||
sector.setSecondsPassed(secPassed);
|
||||
|
||||
boolean attacked = sector.isUnderAttack();
|
||||
|
||||
int wavesPassed = (int)(secPassed*60f / sector.save.meta.rules.waveSpacing);
|
||||
float damage = attacked ? SectorDamage.getDamage(sector.save.meta.secinfo, sector.save.meta.rules.waveSpacing, sector.save.meta.wave, wavesPassed) : 0f;
|
||||
int wavesPassed = (int)(sector.info.secondsPassed*60f / sector.info.waveSpacing);
|
||||
boolean attacked = sector.info.waves;
|
||||
|
||||
if(attacked){
|
||||
sector.setWavesPassed(wavesPassed);
|
||||
sector.info.wavesPassed = wavesPassed;
|
||||
}
|
||||
|
||||
sector.setDamage(damage);
|
||||
float damage = attacked ? SectorDamage.getDamage(sector.info) : 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));
|
||||
|
||||
//if so, just delete the save for now. it's lost.
|
||||
//TODO don't delete it later maybe
|
||||
sector.setExtraItems(new ItemSeq());
|
||||
sector.setDamage(1.01f);
|
||||
}else if(attacked && wavesPassed > 0 && sector.save.meta.wave + wavesPassed >= sector.save.meta.rules.winWave && !sector.hasEnemyBase()){
|
||||
//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.wave + wavesPassed >= sector.info.winWave && !sector.hasEnemyBase()){
|
||||
//autocapture the sector
|
||||
sector.setUnderAttack(false);
|
||||
sector.info.waves = false;
|
||||
|
||||
//fire the event
|
||||
Events.fire(new SectorCaptureEvent(sector));
|
||||
}
|
||||
|
||||
float scl = sector.getProductionScale();
|
||||
|
||||
//export to another sector
|
||||
if(sector.info.destination != null){
|
||||
Sector to = sector.info.destination;
|
||||
if(to.hasBase()){
|
||||
ItemSeq items = new ItemSeq();
|
||||
//calculated exported items to this sector
|
||||
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * scl)));
|
||||
to.addItems(items);
|
||||
}
|
||||
}
|
||||
|
||||
//add production, making sure that it's capped
|
||||
sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * seconds * scl), sector.info.storageCapacity - sector.info.items.get(item))));
|
||||
|
||||
sector.saveInfo();
|
||||
}
|
||||
|
||||
//export to another sector
|
||||
if(sector.save != null && sector.save.meta != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.destination != null){
|
||||
Sector to = sector.save.meta.secinfo.destination;
|
||||
if(to.save != null){
|
||||
float scl = Math.max(1f - sector.getDamage(), 0);
|
||||
ItemSeq items = new ItemSeq();
|
||||
//calculated exported items to this sector
|
||||
sector.save.meta.secinfo.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * scl)));
|
||||
to.addItems(items);
|
||||
//queue random invasions
|
||||
if(!sector.isAttacked() && turn > invasionGracePeriod){
|
||||
//TODO use factors like difficulty for better invasion chance
|
||||
if(sector.near().contains(Sector::hasEnemyBase) && Mathf.chance(baseInvasionChance)){
|
||||
int waveMax = Math.max(sector.info.winWave, sector.isBeingPlayed() ? state.wave : 0) + Mathf.random(2, 4) * 5;
|
||||
float waveSpace = Math.max(sector.info.waveSpacing - Mathf.random(1, 4) * 5 * 60, 40 * 60);
|
||||
|
||||
//assign invasion-related things
|
||||
if(sector.isBeingPlayed()){
|
||||
state.rules.winWave = waveMax;
|
||||
state.rules.waves = true;
|
||||
state.rules.waveSpacing = waveSpace;
|
||||
}else{
|
||||
sector.info.winWave = waveMax;
|
||||
sector.info.waves = true;
|
||||
sector.info.waveSpacing = waveSpace;
|
||||
sector.saveInfo();
|
||||
}
|
||||
|
||||
Events.fire(new SectorInvasionEvent(sector));
|
||||
}
|
||||
}
|
||||
|
||||
//reset time spent to 0
|
||||
sector.setTimeSpent(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -202,7 +230,7 @@ public class Universe{
|
|||
for(Planet planet : content.planets()){
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasSave()){
|
||||
count.add(sector.calculateItems());
|
||||
count.add(sector.items());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,10 @@ public class SaveMeta{
|
|||
public Map map;
|
||||
public int wave;
|
||||
public Rules rules;
|
||||
public SectorInfo secinfo;
|
||||
public StringMap tags;
|
||||
public String[] mods;
|
||||
public boolean hasProduction;
|
||||
|
||||
public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, SectorInfo secinfo, StringMap tags){
|
||||
public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, StringMap tags){
|
||||
this.version = version;
|
||||
this.build = build;
|
||||
this.timestamp = timestamp;
|
||||
|
|
@ -29,8 +27,5 @@ public class SaveMeta{
|
|||
this.rules = rules;
|
||||
this.tags = tags;
|
||||
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
|
||||
this.secinfo = secinfo;
|
||||
|
||||
secinfo.production.each((e, amount) -> hasProduction |= amount.mean > 0.001f);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
map.get("mapname"),
|
||||
map.getInt("wave"),
|
||||
JsonIO.read(Rules.class, map.get("rules", "{}")),
|
||||
JsonIO.read(SectorInfo.class, map.get("secinfo", "{}")),
|
||||
map
|
||||
);
|
||||
}
|
||||
|
|
@ -74,6 +73,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
//prepare campaign data for writing
|
||||
if(state.isCampaign()){
|
||||
state.secinfo.prepare();
|
||||
state.rules.sector.saveInfo();
|
||||
}
|
||||
|
||||
//flush tech node progress
|
||||
|
|
@ -89,7 +89,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
"wave", state.wave,
|
||||
"wavetime", state.wavetime,
|
||||
"stats", JsonIO.write(state.stats),
|
||||
"secinfo", state.isCampaign() ? JsonIO.write(state.secinfo) : "{}",
|
||||
"rules", JsonIO.write(state.rules),
|
||||
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
||||
"width", world.width(),
|
||||
|
|
@ -107,14 +106,13 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
state.wave = map.getInt("wave");
|
||||
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
|
||||
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
||||
state.secinfo = JsonIO.read(SectorInfo.class, map.get("secinfo", "{}"));
|
||||
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
||||
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
|
||||
lastReadBuild = map.getInt("build", -1);
|
||||
|
||||
//load time spent on sector into state
|
||||
//load in sector info
|
||||
if(state.rules.sector != null){
|
||||
state.secinfo.internalTimeSpent = state.rules.sector.getStoredTimeSpent();
|
||||
state.secinfo = state.rules.sector.info;
|
||||
}
|
||||
|
||||
if(!headless){
|
||||
|
|
|
|||
|
|
@ -25,8 +25,11 @@ public class SectorDamage{
|
|||
private static final int maxWavesSimulated = 50;
|
||||
|
||||
/** @return calculated capture progress of the enemy */
|
||||
public static float getDamage(SectorInfo info, float waveSpace, int wave, int wavesPassed){
|
||||
public static float getDamage(SectorInfo info){
|
||||
float health = info.sumHealth;
|
||||
int wavesPassed = info.wavesPassed;
|
||||
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.
|
||||
|
|
@ -76,9 +79,9 @@ public class SectorDamage{
|
|||
}
|
||||
|
||||
/** Applies wave damage based on sector parameters. */
|
||||
public static void applyCalculatedDamage(int wavesPassed){
|
||||
public static void applyCalculatedDamage(){
|
||||
//calculate base damage fraction
|
||||
float damage = getDamage(state.secinfo, state.rules.waveSpacing, state.wave, wavesPassed);
|
||||
float damage = getDamage(state.secinfo);
|
||||
|
||||
//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.5f);
|
||||
|
|
@ -110,6 +113,21 @@ public class SectorDamage{
|
|||
}
|
||||
}
|
||||
|
||||
if(state.secinfo.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);
|
||||
}
|
||||
|
|
@ -120,6 +138,10 @@ public class SectorDamage{
|
|||
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;
|
||||
|
||||
Tile start = spawns.first();
|
||||
|
|
@ -361,7 +383,7 @@ public class SectorDamage{
|
|||
}
|
||||
|
||||
//kill every core if damage is maximum
|
||||
if(damage >= 1){
|
||||
if(fraction >= 1){
|
||||
for(Building c : state.rules.defaultTeam.cores().copy()){
|
||||
c.tile.remove();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ public class Planet extends UnlockableContent{
|
|||
public void updateBaseCoverage(){
|
||||
for(Sector sector : sectors){
|
||||
float sum = 1f;
|
||||
for(Sector other : sector.inRange(2)){
|
||||
for(Sector other : sector.near()){
|
||||
if(other.generateEnemyBase){
|
||||
sum += 1f;
|
||||
}
|
||||
|
|
@ -204,6 +204,10 @@ public class Planet extends UnlockableContent{
|
|||
@Override
|
||||
public void init(){
|
||||
|
||||
for(Sector sector : sectors){
|
||||
sector.loadInfo();
|
||||
}
|
||||
|
||||
if(generator != null){
|
||||
Noise.setSeed(id + 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import arc.struct.*;
|
|||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.Saves.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.graphics.g3d.PlanetGrid.*;
|
||||
import mindustry.world.modules.*;
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ public class Sector{
|
|||
|
||||
public @Nullable SaveSlot save;
|
||||
public @Nullable SectorPreset preset;
|
||||
public SectorInfo info = new SectorInfo();
|
||||
|
||||
/** Number 0-1 indicating the difficulty based on nearby bases. */
|
||||
public float baseCoverage;
|
||||
|
|
@ -38,60 +40,50 @@ public class Sector{
|
|||
this.id = tile.id;
|
||||
}
|
||||
|
||||
public Seq<Sector> inRange(int range){
|
||||
//TODO cleanup/remove
|
||||
if(true){
|
||||
tmpSeq1.clear();
|
||||
neighbors(tmpSeq1::add);
|
||||
|
||||
return tmpSeq1;
|
||||
}
|
||||
|
||||
public Seq<Sector> near(){
|
||||
tmpSeq1.clear();
|
||||
tmpSeq2.clear();
|
||||
tmpSet.clear();
|
||||
near(tmpSeq1::add);
|
||||
|
||||
tmpSeq1.add(this);
|
||||
tmpSet.add(this);
|
||||
for(int i = 0; i < range; i++){
|
||||
while(!tmpSeq1.isEmpty()){
|
||||
Sector sec = tmpSeq1.pop();
|
||||
tmpSet.add(sec);
|
||||
sec.neighbors(other -> {
|
||||
if(tmpSet.add(other)){
|
||||
tmpSeq2.add(other);
|
||||
}
|
||||
});
|
||||
}
|
||||
tmpSeq1.clear();
|
||||
tmpSeq1.addAll(tmpSeq2);
|
||||
}
|
||||
|
||||
tmpSeq3.clear().addAll(tmpSeq2);
|
||||
return tmpSeq3;
|
||||
return tmpSeq1;
|
||||
}
|
||||
|
||||
public void neighbors(Cons<Sector> cons){
|
||||
public void near(Cons<Sector> cons){
|
||||
for(Ptile tile : tile.tiles){
|
||||
cons.get(planet.getSector(tile));
|
||||
}
|
||||
}
|
||||
|
||||
/** @return whether this sector can be landed on at all.
|
||||
* Only sectors adjacent to non-wave sectors can be landed on.
|
||||
* TODO also preset sectors*/
|
||||
* Only sectors adjacent to non-wave sectors can be landed on. */
|
||||
public boolean unlocked(){
|
||||
return hasBase() || (preset != null && preset.alwaysUnlocked);
|
||||
}
|
||||
|
||||
public void saveInfo(){
|
||||
Core.settings.putJson(planet.name + "-s-" + id + "-info", info);
|
||||
}
|
||||
|
||||
public void loadInfo(){
|
||||
info = Core.settings.getJson(planet.name + "-s-" + id + "-info", SectorInfo.class, SectorInfo::new);
|
||||
}
|
||||
|
||||
public float getProductionScale(){
|
||||
return Math.max(1f - info.damage, 0);
|
||||
}
|
||||
|
||||
public boolean isAttacked(){
|
||||
if(isBeingPlayed()) return state.rules.waves;
|
||||
return save != null && info.waves && info.hasCore;
|
||||
}
|
||||
|
||||
/** @return whether the player has a base here. */
|
||||
public boolean hasBase(){
|
||||
return save != null && !save.meta.tags.getBool("nocores") && getDamage() < 1f;
|
||||
return save != null && info.hasCore;
|
||||
}
|
||||
|
||||
/** @return whether the enemy has a generated base here. */
|
||||
public boolean hasEnemyBase(){
|
||||
return generateEnemyBase && (save == null || save.meta.rules.waves);
|
||||
return generateEnemyBase && (save == null || info.waves);
|
||||
}
|
||||
|
||||
public boolean isBeingPlayed(){
|
||||
|
|
@ -99,26 +91,18 @@ public class Sector{
|
|||
return Vars.state.isGame() && Vars.state.rules.sector == this && !Vars.state.gameOver;
|
||||
}
|
||||
|
||||
public String name(){
|
||||
if(preset != null) return preset.localizedName;
|
||||
return info.name == null ? id + "" : info.name;
|
||||
}
|
||||
|
||||
public void setName(String name){
|
||||
info.name = name;
|
||||
saveInfo();
|
||||
}
|
||||
|
||||
public boolean isCaptured(){
|
||||
return save != null && !save.meta.rules.waves;
|
||||
}
|
||||
|
||||
/** @return whether waves are present - if true, any bases here will be attacked.
|
||||
* only applicable to sectors with active player bases. */
|
||||
public boolean isUnderAttack(){
|
||||
return hasBase() && Core.settings.getBool(key("under-attack"), true);
|
||||
}
|
||||
|
||||
public void setUnderAttack(boolean underAttack){
|
||||
Core.settings.put(key("under-attack"), underAttack);
|
||||
}
|
||||
|
||||
public void setWavesPassed(int waves){
|
||||
put("waves-passed", waves);
|
||||
}
|
||||
|
||||
public int getWavesPassed(){
|
||||
return Core.settings.getInt(key("waves-passed"), 0);
|
||||
return save != null && !info.waves;
|
||||
}
|
||||
|
||||
public boolean hasSave(){
|
||||
|
|
@ -143,15 +127,6 @@ public class Sector{
|
|||
return res % 2 == 0 ? res : res + 1;
|
||||
}
|
||||
|
||||
//TODO this should be stored in a more efficient structure, and be updated each turn
|
||||
public ItemSeq getExtraItems(){
|
||||
return Core.settings.getJson(key("extra-items"), ItemSeq.class, ItemSeq::new);
|
||||
}
|
||||
|
||||
public void setExtraItems(ItemSeq stacks){
|
||||
Core.settings.putJson(key("extra-items"), stacks);
|
||||
}
|
||||
|
||||
public void addItem(Item item, int amount){
|
||||
removeItem(item, -amount);
|
||||
}
|
||||
|
|
@ -169,151 +144,27 @@ public class Sector{
|
|||
int cap = state.rules.defaultTeam.core().storageCapacity;
|
||||
items.each((item, amount) -> storage.add(item, Math.min(cap - storage.get(item), amount)));
|
||||
}
|
||||
}else{
|
||||
ItemSeq recv = getExtraItems();
|
||||
|
||||
if(save != null){
|
||||
//"shave off" extra items
|
||||
|
||||
ItemSeq count = new ItemSeq();
|
||||
|
||||
//add items already present
|
||||
count.add(save.meta.secinfo.coreItems);
|
||||
|
||||
count.add(calculateReceivedItems());
|
||||
|
||||
int capacity = save.meta.secinfo.storageCapacity;
|
||||
|
||||
//when over capacity, add that to the extra items
|
||||
count.each((i, a) -> {
|
||||
if(a > capacity){
|
||||
recv.remove(i, (a - capacity));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recv.add(items);
|
||||
|
||||
setExtraItems(recv);
|
||||
}else if(hasBase()){
|
||||
items.each((item, amount) -> info.items.add(item, Math.min(info.storageCapacity - info.items.get(item), amount)));
|
||||
saveInfo();
|
||||
}
|
||||
}
|
||||
|
||||
public ItemSeq calculateItems(){
|
||||
/** @return items currently in this sector, taking into account playing state. */
|
||||
public ItemSeq items(){
|
||||
ItemSeq count = new ItemSeq();
|
||||
|
||||
//for sectors being played on, add items directly
|
||||
if(isBeingPlayed()){
|
||||
count.add(state.rules.defaultTeam.items());
|
||||
}else if(save != null){
|
||||
}else{
|
||||
//add items already present
|
||||
count.add(save.meta.secinfo.coreItems);
|
||||
|
||||
count.add(calculateReceivedItems());
|
||||
|
||||
int capacity = save.meta.secinfo.storageCapacity;
|
||||
|
||||
//validation
|
||||
count.each((item, amount) -> {
|
||||
//ensure positive items
|
||||
if(amount < 0) count.set(item, 0);
|
||||
//cap the items
|
||||
if(amount > capacity) count.set(item, capacity);
|
||||
});
|
||||
count.add(info.items);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public ItemSeq calculateReceivedItems(){
|
||||
ItemSeq count = new ItemSeq();
|
||||
|
||||
if(save != null){
|
||||
long seconds = getSecondsPassed();
|
||||
float scl = Math.max(1f - getDamage(), 0);
|
||||
|
||||
//add produced items
|
||||
save.meta.secinfo.production.each((item, stat) -> count.add(item, (int)(stat.mean * seconds * scl)));
|
||||
|
||||
//add received items
|
||||
count.add(getExtraItems());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
//TODO these methods should maybe move somewhere else and/or be contained in a data object
|
||||
public void setSpawnPosition(int position){
|
||||
put("spawn-position", position);
|
||||
}
|
||||
|
||||
/** Only valid after this sector has been landed on once. */
|
||||
//TODO move to sector data?
|
||||
public int getSpawnPosition(){
|
||||
return Core.settings.getInt(key("spawn-position"), Point2.pack(world.width() / 2, world.height() / 2));
|
||||
}
|
||||
|
||||
/** @return sector damage from enemy, 0 to 1 */
|
||||
public float getDamage(){
|
||||
//dead sector
|
||||
if(save != null & save.meta.tags.getBool("nocores")) return 1.01f;
|
||||
return Core.settings.getFloat(key("damage"), 0f);
|
||||
}
|
||||
|
||||
public void setDamage(float damage){
|
||||
put("damage", damage);
|
||||
}
|
||||
|
||||
/** @return time spent in this sector this turn in ticks. */
|
||||
public float getTimeSpent(){
|
||||
//return currently counting time spent if being played on
|
||||
if(isBeingPlayed()) return state.secinfo.internalTimeSpent;
|
||||
|
||||
//else return the stored value
|
||||
return getStoredTimeSpent();
|
||||
}
|
||||
|
||||
public void setTimeSpent(float time){
|
||||
put("time-spent", time);
|
||||
|
||||
//update counting time
|
||||
if(isBeingPlayed()){
|
||||
state.secinfo.internalTimeSpent = time;
|
||||
}
|
||||
}
|
||||
|
||||
public String displayTimeRemaining(){
|
||||
float amount = Vars.turnDuration - getTimeSpent();
|
||||
int seconds = (int)(amount / 60);
|
||||
int sf = seconds % 60;
|
||||
return (seconds / 60) + ":" + (sf < 10 ? "0" : "") + sf;
|
||||
}
|
||||
|
||||
/** @return the stored amount of time spent in this sector this turn in ticks.
|
||||
* Do not use unless you know what you're doing. */
|
||||
public float getStoredTimeSpent(){
|
||||
return Core.settings.getFloat(key("time-spent"));
|
||||
}
|
||||
|
||||
public void setSecondsPassed(int number){
|
||||
put("secondsi-passed", number);
|
||||
}
|
||||
|
||||
/** @return how much time has passed in this sector without the player resuming here.
|
||||
* Used for resource production calculations. */
|
||||
public int getSecondsPassed(){
|
||||
return Core.settings.getInt(key("secondsi-passed"));
|
||||
}
|
||||
|
||||
//TODO this is terrible
|
||||
private String key(String key){
|
||||
return planet.name + "-s-" + id + "-" + key;
|
||||
}
|
||||
|
||||
//TODO this is terrible
|
||||
private void put(String key, Object value){
|
||||
Core.settings.put(key(key), value);
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return planet.name + "#" + id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import arc.func.*;
|
|||
public class IntFormat{
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
private final String text;
|
||||
private int lastValue = Integer.MIN_VALUE;
|
||||
private int lastValue = Integer.MIN_VALUE, lastValue2 = Integer.MIN_VALUE;
|
||||
private Func<Integer, String> converter = String::valueOf;
|
||||
|
||||
public IntFormat(String text){
|
||||
|
|
@ -30,4 +30,14 @@ public class IntFormat{
|
|||
lastValue = value;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public CharSequence get(int value1, int value2){
|
||||
if(lastValue != value1 || lastValue2 != value2){
|
||||
builder.setLength(0);
|
||||
builder.append(Core.bundle.format(text, value1, value2));
|
||||
}
|
||||
lastValue = value1;
|
||||
lastValue2 = value2;
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import static mindustry.gen.Tex.*;
|
|||
|
||||
@StyleDefaults
|
||||
public class Styles{
|
||||
//TODO all these names are inconsistent and not descriptive
|
||||
public static Drawable black, black9, black8, black6, black3, black5, none, flatDown, flatOver;
|
||||
public static ButtonStyle defaultb, waveb;
|
||||
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
|
||||
|
|
|
|||
|
|
@ -34,13 +34,6 @@ public class PausedDialog extends BaseDialog{
|
|||
});
|
||||
|
||||
if(!mobile){
|
||||
//TODO localize + move to other wave menu
|
||||
cont.label(() -> state.getSector() == null || state.rules.winWave <= 0 || state.getSector().isCaptured() ? "" :
|
||||
(state.rules.winWave > 0 && !state.getSector().isCaptured() ?
|
||||
(state.wave >= state.rules.winWave ? "\n[lightgray]Defeat remaining enemies to capture" : "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture") : ""))
|
||||
.visible(() -> state.getSector() != null).colspan(2);
|
||||
cont.row();
|
||||
|
||||
float dw = 220f;
|
||||
cont.defaults().width(dw).height(55).pad(5f);
|
||||
|
||||
|
|
|
|||
|
|
@ -217,9 +217,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||
public void renderProjections(){
|
||||
if(hovered != null){
|
||||
planets.drawPlane(hovered, () -> {
|
||||
Draw.color(hovered.isUnderAttack() ? Pal.remove : Color.white, Pal.accent, Mathf.absin(5f, 1f));
|
||||
Draw.color(hovered.isAttacked() ? Pal.remove : Color.white, Pal.accent, Mathf.absin(5f, 1f));
|
||||
|
||||
TextureRegion icon = hovered.locked() && !canSelect(hovered) ? Icon.lock.getRegion() : hovered.isUnderAttack() ? Icon.warning.getRegion() : null;
|
||||
TextureRegion icon = hovered.locked() && !canSelect(hovered) ? Icon.lock.getRegion() : hovered.isAttacked() ? Icon.warning.getRegion() : null;
|
||||
|
||||
if(icon != null){
|
||||
Draw.rect(icon, 0, 0);
|
||||
|
|
@ -352,69 +352,80 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||
stable.clear();
|
||||
stable.background(Styles.black6);
|
||||
|
||||
stable.add("[accent]" + (sector.preset == null ? sector.id : sector.preset.localizedName)).row();
|
||||
stable.table(title -> {
|
||||
title.add("[accent]" + sector.name());
|
||||
if(sector.preset == null){
|
||||
title.button(Icon.pencilSmall, Styles.clearPartiali, () -> {
|
||||
ui.showTextInput("@sectors.rename", "@name", 20, sector.name(), v -> {
|
||||
sector.setName(v);
|
||||
updateSelected();
|
||||
});
|
||||
}).size(40f).padLeft(4);
|
||||
}
|
||||
}).row();
|
||||
|
||||
stable.image().color(Pal.accent).fillX().height(3f).pad(3f).row();
|
||||
stable.add(sector.save != null ? sector.save.getPlayTime() : "@sectors.unexplored").row();
|
||||
if(sector.isUnderAttack() || sector.hasEnemyBase()){
|
||||
|
||||
if(sector.isAttacked() || sector.hasEnemyBase()){
|
||||
stable.add("[accent]Difficulty: " + (int)(sector.baseCoverage * 10)).row();
|
||||
}
|
||||
|
||||
if(sector.isUnderAttack()){
|
||||
if(sector.isAttacked()){
|
||||
//TODO localize when finalized
|
||||
//these mechanics are likely to change and as such are not added to the bundle
|
||||
stable.add("[scarlet]Under attack!");
|
||||
stable.row();
|
||||
stable.add("[accent]" + (int)(sector.getDamage() * 100) + "% damaged");
|
||||
stable.add("[accent]" + (int)(sector.info.damage * 100) + "% damaged");
|
||||
stable.row();
|
||||
}
|
||||
|
||||
if(sector.save != null){
|
||||
stable.add("@sectors.resources").row();
|
||||
stable.table(t -> {
|
||||
|
||||
if(sector.save != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.resources.any()){
|
||||
if(sector.info.resources.any()){
|
||||
t.left();
|
||||
int idx = 0;
|
||||
int max = 5;
|
||||
for(UnlockableContent c : sector.save.meta.secinfo.resources){
|
||||
for(UnlockableContent c : sector.info.resources){
|
||||
t.image(c.icon(Cicon.small)).padRight(3);
|
||||
if(++idx % max == 0) t.row();
|
||||
}
|
||||
}else{
|
||||
t.add("@unknown").color(Color.lightGray);
|
||||
}
|
||||
|
||||
|
||||
}).fillX().row();
|
||||
}
|
||||
|
||||
//production
|
||||
if(sector.hasBase() && sector.save.meta.hasProduction){
|
||||
stable.add("@sectors.production").row();
|
||||
stable.table(t -> {
|
||||
t.left();
|
||||
if(sector.hasBase()){
|
||||
Table t = new Table().left();
|
||||
|
||||
float scl = Math.max(1f - sector.getDamage(), 0);
|
||||
float scl = sector.getProductionScale();
|
||||
|
||||
sector.save.meta.secinfo.production.each((item, stat) -> {
|
||||
int total = (int)(stat.mean * 60 * scl);
|
||||
if(total > 1){
|
||||
t.image(item.icon(Cicon.small)).padRight(3);
|
||||
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray);
|
||||
t.row();
|
||||
}
|
||||
});
|
||||
}).row();
|
||||
sector.info.production.each((item, stat) -> {
|
||||
int total = (int)(stat.mean * 60 * scl);
|
||||
if(total > 1){
|
||||
t.image(item.icon(Cicon.small)).padRight(3);
|
||||
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray);
|
||||
t.row();
|
||||
}
|
||||
});
|
||||
|
||||
if(t.getChildren().any()){
|
||||
stable.add("@sectors.production").row();
|
||||
stable.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
//stored resources
|
||||
if(sector.hasBase() && sector.save.meta.secinfo.coreItems.total > 0){
|
||||
if(sector.hasBase() && sector.info.items.total > 0){
|
||||
stable.add("@sectors.stored").row();
|
||||
stable.table(t -> {
|
||||
t.left();
|
||||
|
||||
t.table(res -> {
|
||||
ItemSeq items = sector.calculateItems();
|
||||
ItemSeq items = sector.items();
|
||||
|
||||
int i = 0;
|
||||
for(ItemStack stack : items){
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public class ResearchDialog extends BaseDialog{
|
|||
for(Planet planet : content.planets()){
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasSave()){
|
||||
ItemSeq cached = sector.calculateItems();
|
||||
ItemSeq cached = sector.items();
|
||||
add(cached);
|
||||
cache.put(sector, cached);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,12 +71,17 @@ public class HudFragment extends Fragment{
|
|||
//TODO details and stuff
|
||||
Events.on(SectorCaptureEvent.class, e ->{
|
||||
//TODO localize
|
||||
showToast("Sector [accent]" + (e.sector.isBeingPlayed() ? "" : e.sector.id + " ") + "[]captured!");
|
||||
showToast("Sector [accent]" + (e.sector.isBeingPlayed() ? "" : e.sector.name() + " ") + "[white]captured!");
|
||||
});
|
||||
|
||||
//TODO localize
|
||||
Events.on(SectorLoseEvent.class, e -> {
|
||||
showToast(Icon.warning, "Sector " + e.sector.id + " [scarlet]lost!");
|
||||
showToast(Icon.warning, "Sector [accent]" + e.sector.name() + "[white] lost!");
|
||||
});
|
||||
|
||||
//TODO localize
|
||||
Events.on(SectorInvasionEvent.class, e -> {
|
||||
showToast(Icon.warning, "Sector [accent]" + e.sector.name() + "[white] under attack!");
|
||||
});
|
||||
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
|
|
@ -589,6 +594,7 @@ public class HudFragment extends Fragment{
|
|||
StringBuilder ibuild = new StringBuilder();
|
||||
|
||||
IntFormat wavef = new IntFormat("wave");
|
||||
IntFormat wavefc = new IntFormat("wave.cap");
|
||||
IntFormat enemyf = new IntFormat("wave.enemy");
|
||||
IntFormat enemiesf = new IntFormat("wave.enemies");
|
||||
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
|
||||
|
|
@ -714,7 +720,11 @@ public class HudFragment extends Fragment{
|
|||
|
||||
table.labelWrap(() -> {
|
||||
builder.setLength(0);
|
||||
builder.append(wavef.get(state.wave));
|
||||
if(state.rules.winWave > 1 && state.rules.winWave >= state.wave && state.isCampaign()){
|
||||
builder.append(wavefc.get(state.wave, state.rules.winWave));
|
||||
}else{
|
||||
builder.append(wavef.get(state.wave));
|
||||
}
|
||||
builder.append("\n");
|
||||
|
||||
if(state.enemies > 0){
|
||||
|
|
@ -727,7 +737,7 @@ public class HudFragment extends Fragment{
|
|||
}
|
||||
|
||||
if(state.rules.waveTimer){
|
||||
builder.append((logic.isWaitingWave() ? Core.bundle.get("wave.waveInProgress") : ( waitingf.get((int)(state.wavetime/60)))));
|
||||
builder.append((logic.isWaitingWave() ? Core.bundle.get("wave.waveInProgress") : (waitingf.get((int)(state.wavetime/60)))));
|
||||
}else if(state.enemies == 0){
|
||||
builder.append(Core.bundle.get("waiting"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,6 +267,10 @@ public class Tile implements Position, QuadTreeObject, Displayable{
|
|||
Geometry.circle(x, y, world.width(), world.height(), radius, cons);
|
||||
}
|
||||
|
||||
public void circle(int radius, Cons<Tile> cons){
|
||||
circle(radius, (x, y) -> cons.get(world.rawTile(x, y)));
|
||||
}
|
||||
|
||||
public void recache(){
|
||||
if(!headless && !world.isGenerating()){
|
||||
renderer.blocks.floor.recacheTile(this);
|
||||
|
|
@ -332,6 +336,11 @@ public class Tile implements Position, QuadTreeObject, Displayable{
|
|||
recache();
|
||||
}
|
||||
|
||||
/** Sets the overlay without a recache. */
|
||||
public void setOverlayQuiet(Block block){
|
||||
this.overlay = (Floor)block;
|
||||
}
|
||||
|
||||
public void clearOverlay(){
|
||||
setOverlayID((short)0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,9 +121,7 @@ public class LaunchPad extends Block{
|
|||
|
||||
return Core.bundle.format("launch.destination",
|
||||
dest == null ? Core.bundle.get("sectors.nonelaunch") :
|
||||
dest.preset == null ?
|
||||
"[accent]Sector " + dest.id :
|
||||
"[accent]" + dest.preset.localizedName);
|
||||
"[accent]" + dest.name());
|
||||
}).pad(4);
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +211,7 @@ public class LaunchPad extends Block{
|
|||
//actually launch the items upon removal
|
||||
if(team() == state.rules.defaultTeam){
|
||||
if(destsec != null && (destsec != state.rules.sector || net.client())){
|
||||
ItemSeq dest = destsec.getExtraItems();
|
||||
ItemSeq dest = new ItemSeq();
|
||||
|
||||
for(ItemStack stack : stacks){
|
||||
dest.add(stack);
|
||||
|
|
@ -223,7 +221,7 @@ public class LaunchPad extends Block{
|
|||
Events.fire(new LaunchItemEvent(stack));
|
||||
}
|
||||
|
||||
destsec.setExtraItems(dest);
|
||||
destsec.addItems(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ public class Conveyor extends Block implements Autotiler{
|
|||
lastInserted = build.lastInserted;
|
||||
mid = build.mid;
|
||||
minitem = build.minitem;
|
||||
items.addAll(build.items);
|
||||
items.add(build.items);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ public class StackConveyor extends Block implements Autotiler{
|
|||
if(front() instanceof StackConveyorBuild e && e.team == team){
|
||||
// sleep if its occupied
|
||||
if(e.link == -1){
|
||||
e.items.addAll(items);
|
||||
e.items.add(items);
|
||||
e.lastItem = lastItem;
|
||||
e.link = tile.pos();
|
||||
// ▲ to | from ▼
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class StorageBlock extends Block{
|
|||
public void overwrote(Seq<Building> previous){
|
||||
for(Building other : previous){
|
||||
if(other.items != null){
|
||||
items.addAll(other.items);
|
||||
items.add(other.items);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -243,6 +243,16 @@ public class ItemModule extends BlockModule{
|
|||
}
|
||||
}
|
||||
|
||||
public void add(ItemSeq stacks){
|
||||
stacks.each(this::add);
|
||||
}
|
||||
|
||||
public void add(ItemModule items){
|
||||
for(int i = 0; i < items.items.length; i++){
|
||||
add(i, items.items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Item item, int amount){
|
||||
add(item.id, amount);
|
||||
}
|
||||
|
|
@ -261,12 +271,6 @@ public class ItemModule extends BlockModule{
|
|||
}
|
||||
}
|
||||
|
||||
public void addAll(ItemModule items){
|
||||
for(int i = 0; i < items.items.length; i++){
|
||||
add(i, items.items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Item item, int amount){
|
||||
amount = Math.min(amount, items[item.id]);
|
||||
|
||||
|
|
|
|||
|
|
@ -153,9 +153,9 @@ public class Generators{
|
|||
|
||||
ImagePacker.generate("cracks", () -> {
|
||||
RidgedPerlin r = new RidgedPerlin(1, 3);
|
||||
for(int size = 1; size <= Block.maxCrackSize; size++){
|
||||
for(int size = 1; size <= BlockRenderer.maxCrackSize; size++){
|
||||
int dim = size * 32;
|
||||
int steps = Block.crackRegions;
|
||||
int steps = BlockRenderer.crackRegions;
|
||||
for(int i = 0; i < steps; i++){
|
||||
float fract = i / (float)steps;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue