Merge remote-tracking branch 'upstream/master' into ground-support-heal
This commit is contained in:
@@ -285,6 +285,7 @@ selectschematic = [accent][[{0}][] to select+copy
|
|||||||
pausebuilding = [accent][[{0}][] to pause building
|
pausebuilding = [accent][[{0}][] to pause building
|
||||||
resumebuilding = [scarlet][[{0}][] to resume building
|
resumebuilding = [scarlet][[{0}][] to resume building
|
||||||
wave = [accent]Wave {0}
|
wave = [accent]Wave {0}
|
||||||
|
wave.cap = [accent]Wave {0}/{1}
|
||||||
wave.waiting = [lightgray]Wave in {0}
|
wave.waiting = [lightgray]Wave in {0}
|
||||||
wave.waveInProgress = [lightgray]Wave in progress
|
wave.waveInProgress = [lightgray]Wave in progress
|
||||||
waiting = [lightgray]Waiting...
|
waiting = [lightgray]Waiting...
|
||||||
@@ -478,7 +479,7 @@ requirement.research = Research {0}
|
|||||||
requirement.capture = Capture {0}
|
requirement.capture = Capture {0}
|
||||||
bestwave = [lightgray]Best Wave: {0}
|
bestwave = [lightgray]Best Wave: {0}
|
||||||
launch.text = Launch
|
launch.text = Launch
|
||||||
campaign.multiplayer = While playing multiplayer in campaign, you can only research using items from [accent]your[] sectors, [scarlet]not[] the host's sector that you are on right now.\n\nTo get items to [accent]your[] sectors in multiplayer, use a [accent]launch pad[].
|
research.multiplayer = Only the host can research items.
|
||||||
uncover = Uncover
|
uncover = Uncover
|
||||||
configure = Configure Loadout
|
configure = Configure Loadout
|
||||||
#TODO
|
#TODO
|
||||||
@@ -521,6 +522,7 @@ sectors.resume = Resume
|
|||||||
sectors.launch = Launch
|
sectors.launch = Launch
|
||||||
sectors.select = Select
|
sectors.select = Select
|
||||||
sectors.nonelaunch = [lightgray]none (sun)
|
sectors.nonelaunch = [lightgray]none (sun)
|
||||||
|
sectors.rename = Rename Sector
|
||||||
|
|
||||||
planet.serpulo.name = Serpulo
|
planet.serpulo.name = Serpulo
|
||||||
#TODO better name
|
#TODO better name
|
||||||
@@ -695,7 +697,6 @@ setting.linear.name = Linear Filtering
|
|||||||
setting.hints.name = Hints
|
setting.hints.name = Hints
|
||||||
setting.flow.name = Display Resource Flow Rate
|
setting.flow.name = Display Resource Flow Rate
|
||||||
setting.buildautopause.name = Auto-Pause Building
|
setting.buildautopause.name = Auto-Pause Building
|
||||||
setting.mapcenter.name = Auto Center Map To Player
|
|
||||||
setting.animatedwater.name = Animated Fluids
|
setting.animatedwater.name = Animated Fluids
|
||||||
setting.animatedshields.name = Animated Shields
|
setting.animatedshields.name = Animated Shields
|
||||||
setting.antialias.name = Antialias[lightgray] (requires restart)[]
|
setting.antialias.name = Antialias[lightgray] (requires restart)[]
|
||||||
|
|||||||
Binary file not shown.
@@ -86,8 +86,10 @@ public class Vars implements Loadable{
|
|||||||
public static final float logicItemTransferRange = 45f;
|
public static final float logicItemTransferRange = 45f;
|
||||||
/** duration of time between turns in ticks */
|
/** duration of time between turns in ticks */
|
||||||
public static final float turnDuration = 2 * Time.toMinutes;
|
public static final float turnDuration = 2 * Time.toMinutes;
|
||||||
/** turns needed to destroy a sector completely */
|
/** chance of an invasion per turn, 1 = 100% */
|
||||||
public static final float sectorDestructionTurns = 2f;
|
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 */
|
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||||
public static final float minArmorDamage = 0.1f;
|
public static final float minArmorDamage = 0.1f;
|
||||||
/** launch animation duration */
|
/** launch animation duration */
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ public class Pathfinder implements Runnable{
|
|||||||
* Data for a flow field to some set of destinations.
|
* Data for a flow field to some set of destinations.
|
||||||
* Concrete subclasses must specify a way to fetch costs and destinations.
|
* Concrete subclasses must specify a way to fetch costs and destinations.
|
||||||
* */
|
* */
|
||||||
static abstract class Flowfield{
|
public static abstract class Flowfield{
|
||||||
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
||||||
protected int refreshRate;
|
protected int refreshRate;
|
||||||
/** Team this path is for. Set before using. */
|
/** Team this path is for. Set before using. */
|
||||||
@@ -459,7 +459,7 @@ public class Pathfinder implements Runnable{
|
|||||||
protected PathCost cost = costTypes.get(costGround);
|
protected PathCost cost = costTypes.get(costGround);
|
||||||
|
|
||||||
/** costs of getting to a specific tile */
|
/** costs of getting to a specific tile */
|
||||||
int[][] weights;
|
public int[][] weights;
|
||||||
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
||||||
int[][] searches;
|
int[][] searches;
|
||||||
/** search frontier, these are Pos objects */
|
/** search frontier, these are Pos objects */
|
||||||
|
|||||||
@@ -23,11 +23,21 @@ public class WaveSpawner{
|
|||||||
private Seq<Tile> spawns = new Seq<>();
|
private Seq<Tile> spawns = new Seq<>();
|
||||||
private boolean spawning = false;
|
private boolean spawning = false;
|
||||||
private boolean any = false;
|
private boolean any = false;
|
||||||
|
private Tile firstSpawn = null;
|
||||||
|
|
||||||
public WaveSpawner(){
|
public WaveSpawner(){
|
||||||
Events.on(WorldLoadEvent.class, e -> reset());
|
Events.on(WorldLoadEvent.class, e -> reset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Tile getFirstSpawn(){
|
||||||
|
firstSpawn = null;
|
||||||
|
eachGroundSpawn((cx, cy) -> {
|
||||||
|
firstSpawn = world.tile(cx, cy);
|
||||||
|
});
|
||||||
|
return firstSpawn;
|
||||||
|
}
|
||||||
|
|
||||||
public int countSpawns(){
|
public int countSpawns(){
|
||||||
return spawns.size;
|
return spawns.size;
|
||||||
}
|
}
|
||||||
@@ -38,7 +48,7 @@ public class WaveSpawner{
|
|||||||
|
|
||||||
/** @return true if the player is near a ground spawn point. */
|
/** @return true if the player is near a ground spawn point. */
|
||||||
public boolean playerNear(){
|
public boolean playerNear(){
|
||||||
return !player.dead() && spawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
|
return state.rules.waves && !player.dead() && spawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void spawnEnemies(){
|
public void spawnEnemies(){
|
||||||
@@ -47,7 +57,7 @@ public class WaveSpawner{
|
|||||||
for(SpawnGroup group : state.rules.spawns){
|
for(SpawnGroup group : state.rules.spawns){
|
||||||
if(group.type == null) continue;
|
if(group.type == null) continue;
|
||||||
|
|
||||||
int spawned = group.getUnitsSpawned(state.wave - 1);
|
int spawned = group.getSpawned(state.wave - 1);
|
||||||
|
|
||||||
if(group.type.flying){
|
if(group.type.flying){
|
||||||
float spread = margin / 1.5f;
|
float spread = margin / 1.5f;
|
||||||
@@ -89,6 +99,10 @@ public class WaveSpawner{
|
|||||||
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true));
|
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void eachGroundSpawn(Intc2 cons){
|
||||||
|
eachGroundSpawn((x, y, shock) -> cons.get(world.toTile(x), world.toTile(y)));
|
||||||
|
}
|
||||||
|
|
||||||
private void eachGroundSpawn(SpawnConsumer cons){
|
private void eachGroundSpawn(SpawnConsumer cons){
|
||||||
for(Tile spawn : spawns){
|
for(Tile spawn : spawns){
|
||||||
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import mindustry.ai.formations.*;
|
|||||||
import mindustry.entities.units.*;
|
import mindustry.entities.units.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
|
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||||
|
|
||||||
public class FormationAI extends AIController implements FormationMember{
|
public class FormationAI extends AIController implements FormationMember{
|
||||||
public Unit leader;
|
public Unit leader;
|
||||||
@@ -57,6 +58,30 @@ public class FormationAI extends AIController implements FormationMember{
|
|||||||
}else{
|
}else{
|
||||||
unit.moveAt(realtarget.sub(unit).limit(type.speed));
|
unit.moveAt(realtarget.sub(unit).limit(type.speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(unit instanceof Minerc mine && leader instanceof Minerc com){
|
||||||
|
if(mine.validMine(com.mineTile())){
|
||||||
|
mine.mineTile(com.mineTile());
|
||||||
|
|
||||||
|
CoreBuild core = unit.team.core();
|
||||||
|
|
||||||
|
if(core != null && com.mineTile().drop() != null && unit.within(core, unit.type().range) && !unit.acceptsItem(com.mineTile().drop())){
|
||||||
|
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||||
|
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||||
|
|
||||||
|
unit.clearItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
mine.mineTile(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(unit instanceof Builderc build && leader instanceof Builderc com && com.activelyBuilding()){
|
||||||
|
build.clearBuilding();
|
||||||
|
build.addBuild(com.buildPlan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ public class MinerAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(mining){
|
if(mining){
|
||||||
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
|
if(timer.get(timerTarget2, 60 * 4) || targetItem == null){
|
||||||
|
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
//core full of the target item, do nothing
|
//core full of the target item, do nothing
|
||||||
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class RepairAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
if(!target.within(unit, unit.type().range * 0.65f)){
|
if(!target.within(unit, unit.type().range * 0.65f) && target instanceof Building){
|
||||||
moveTo(target, unit.type().range * 0.65f);
|
moveTo(target, unit.type().range * 0.65f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,12 +33,14 @@ public class RepairAI extends AIController{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateTargeting(){
|
protected void updateTargeting(){
|
||||||
target = Units.findDamagedTile(unit.team, unit.x, unit.y);
|
Building target = Units.findDamagedTile(unit.team, unit.x, unit.y);
|
||||||
|
|
||||||
if(target instanceof ConstructBuild) target = null;
|
if(target instanceof ConstructBuild) target = null;
|
||||||
|
|
||||||
if(target == null){
|
if(target == null){
|
||||||
super.updateTargeting();
|
super.updateTargeting();
|
||||||
|
}else{
|
||||||
|
this.target = target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1713,7 +1713,7 @@ public class Blocks implements ContentList{
|
|||||||
despawnEffect = Fx.instBomb;
|
despawnEffect = Fx.instBomb;
|
||||||
trailSpacing = 20f;
|
trailSpacing = 20f;
|
||||||
damage = 1350;
|
damage = 1350;
|
||||||
tileDamageMultiplier = 0.5f;
|
tileDamageMultiplier = 0.3f;
|
||||||
speed = brange;
|
speed = brange;
|
||||||
hitShake = 6f;
|
hitShake = 6f;
|
||||||
ammoMultiplier = 1f;
|
ammoMultiplier = 1f;
|
||||||
|
|||||||
@@ -510,7 +510,7 @@ public class Bullets implements ContentList{
|
|||||||
speed = 4f;
|
speed = 4f;
|
||||||
knockback = 1.3f;
|
knockback = 1.3f;
|
||||||
puddleSize = 8f;
|
puddleSize = 8f;
|
||||||
damage = 6f;
|
damage = 5f;
|
||||||
drag = 0.001f;
|
drag = 0.001f;
|
||||||
ammoMultiplier = 2f;
|
ammoMultiplier = 2f;
|
||||||
statusDuration = 60f * 4f;
|
statusDuration = 60f * 4f;
|
||||||
|
|||||||
@@ -160,9 +160,7 @@ public class Control implements ApplicationListener, Loadable{
|
|||||||
|
|
||||||
//delete the save, it is gone.
|
//delete the save, it is gone.
|
||||||
if(saves.getCurrent() != null && !state.rules.tutorial){
|
if(saves.getCurrent() != null && !state.rules.tutorial){
|
||||||
Sector sector = state.getSector();
|
saves.getCurrent().save();
|
||||||
sector.save = null;
|
|
||||||
saves.getCurrent().delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -281,20 +279,17 @@ public class Control implements ApplicationListener, Loadable{
|
|||||||
slot.load();
|
slot.load();
|
||||||
slot.setAutosave(true);
|
slot.setAutosave(true);
|
||||||
state.rules.sector = sector;
|
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 there is no base, simulate a new game and place the right loadout at the spawn position
|
||||||
//TODO this is broken?
|
|
||||||
if(state.rules.defaultTeam.cores().isEmpty()){
|
if(state.rules.defaultTeam.cores().isEmpty()){
|
||||||
|
//reset wave so things are more fair
|
||||||
|
state.wave = 1;
|
||||||
|
|
||||||
//kill all friendly units, since they should be dead anwyay
|
//kill all units, since they should be dead anwyay
|
||||||
for(Unit unit : Groups.unit){
|
Groups.unit.clear();
|
||||||
if(unit.team() == state.rules.defaultTeam){
|
|
||||||
unit.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Tile spawn = world.tile(sector.getSpawnPosition());
|
Tile spawn = world.tile(sector.info.spawnPosition);
|
||||||
//TODO PLACE CORRECT LOADOUT
|
|
||||||
Schematics.placeLoadout(universe.getLastLoadout(), spawn.x, spawn.y);
|
Schematics.placeLoadout(universe.getLastLoadout(), spawn.x, spawn.y);
|
||||||
|
|
||||||
//set up camera/player locations
|
//set up camera/player locations
|
||||||
@@ -317,7 +312,6 @@ public class Control implements ApplicationListener, Loadable{
|
|||||||
}else{
|
}else{
|
||||||
net.reset();
|
net.reset();
|
||||||
logic.reset();
|
logic.reset();
|
||||||
sector.setSecondsPassed(0);
|
|
||||||
world.loadSector(sector);
|
world.loadSector(sector);
|
||||||
state.rules.sector = sector;
|
state.rules.sector = sector;
|
||||||
//assign origin when launching
|
//assign origin when launching
|
||||||
|
|||||||
@@ -5,16 +5,17 @@ import arc.math.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.core.GameState.*;
|
import mindustry.core.GameState.*;
|
||||||
|
import mindustry.ctype.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.game.Teams.*;
|
import mindustry.game.Teams.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
|
import mindustry.maps.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.type.Weather.*;
|
import mindustry.type.Weather.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.*;
|
import mindustry.world.blocks.*;
|
||||||
import mindustry.world.blocks.ConstructBlock.*;
|
import mindustry.world.blocks.ConstructBlock.*;
|
||||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -84,44 +85,42 @@ public class Logic implements ApplicationListener{
|
|||||||
Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack));
|
Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack));
|
||||||
|
|
||||||
//when loading a 'damaged' sector, propagate the damage
|
//when loading a 'damaged' sector, propagate the damage
|
||||||
Events.on(WorldLoadEvent.class, e -> {
|
Events.on(SaveLoadEvent.class, e -> {
|
||||||
if(state.isCampaign()){
|
if(state.isCampaign()){
|
||||||
long seconds = state.rules.sector.getSecondsPassed();
|
state.secinfo.write();
|
||||||
CoreBuild core = state.rules.defaultTeam.core();
|
|
||||||
//THE WAVES NEVER END
|
|
||||||
state.rules.waves = true;
|
|
||||||
|
|
||||||
//apply fractional damage based on how many turns have passed for this sector
|
//how much wave time has passed
|
||||||
//float turnsPassed = seconds / (turnDuration / 60f);
|
int wavesPassed = state.secinfo.wavesPassed;
|
||||||
|
|
||||||
//TODO sector damage disabled for now
|
//wave has passed, remove all enemies, they are assumed to be dead
|
||||||
//if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
|
if(wavesPassed > 0){
|
||||||
// SectorDamage.apply(turnsPassed / sectorDestructionTurns);
|
Groups.unit.each(u -> {
|
||||||
//}
|
if(u.team == state.rules.waveTeam){
|
||||||
|
u.remove();
|
||||||
//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);
|
//simulate passing of waves
|
||||||
}
|
if(wavesPassed > 0){
|
||||||
|
//simulate wave counter moving forward
|
||||||
|
state.wave += wavesPassed;
|
||||||
|
state.wavetime = state.rules.waveSpacing;
|
||||||
|
|
||||||
|
SectorDamage.applyCalculatedDamage();
|
||||||
|
}
|
||||||
|
|
||||||
|
//reset values
|
||||||
|
state.secinfo.damage = 0f;
|
||||||
|
state.secinfo.wavesPassed = 0;
|
||||||
|
state.secinfo.hasCore = true;
|
||||||
|
state.secinfo.secondsPassed = 0;
|
||||||
|
|
||||||
|
state.rules.sector.saveInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Events.on(WorldLoadEvent.class, e -> {
|
||||||
//enable infinite ammo for wave team by default
|
//enable infinite ammo for wave team by default
|
||||||
state.rules.waveTeam.rules().infiniteAmmo = true;
|
state.rules.waveTeam.rules().infiniteAmmo = true;
|
||||||
|
|
||||||
@@ -129,6 +128,13 @@ public class Logic implements ApplicationListener{
|
|||||||
Core.settings.manualSave();
|
Core.settings.manualSave();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//sync research
|
||||||
|
Events.on(ResearchEvent.class, e -> {
|
||||||
|
if(net.server()){
|
||||||
|
Call.researched(e.content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds starting items, resets wave time, and sets state to playing. */
|
/** Adds starting items, resets wave time, and sets state to playing. */
|
||||||
@@ -168,11 +174,6 @@ public class Logic implements ApplicationListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void skipWave(){
|
public void skipWave(){
|
||||||
if(state.isCampaign()){
|
|
||||||
//warp time spent forward because the wave was just skipped.
|
|
||||||
state.secinfo.internalTimeSpent += state.wavetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.wavetime = 0;
|
state.wavetime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,8 +200,6 @@ public class Logic implements ApplicationListener{
|
|||||||
state.rules.waves = false;
|
state.rules.waves = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO capturing is disabled
|
|
||||||
/*
|
|
||||||
//if there's a "win" wave and no enemies are present, win automatically
|
//if there's a "win" wave and no enemies are present, win automatically
|
||||||
if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){
|
if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){
|
||||||
//the sector has been conquered - waves get disabled
|
//the sector has been conquered - waves get disabled
|
||||||
@@ -213,7 +212,7 @@ public class Logic implements ApplicationListener{
|
|||||||
if(!headless){
|
if(!headless){
|
||||||
control.saves.saveSector(state.rules.sector);
|
control.saves.saveSector(state.rules.sector);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
}else{
|
}else{
|
||||||
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
|
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
|
||||||
state.gameOver = true;
|
state.gameOver = true;
|
||||||
@@ -266,6 +265,15 @@ public class Logic implements ApplicationListener{
|
|||||||
netClient.setQuiet();
|
netClient.setQuiet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//called when the remote server researches something
|
||||||
|
@Remote
|
||||||
|
public static void researched(Content content){
|
||||||
|
if(!(content instanceof UnlockableContent u)) return;
|
||||||
|
|
||||||
|
state.rules.researched.add(u.name);
|
||||||
|
ui.hudfrag.showUnlock(u);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose(){
|
public void dispose(){
|
||||||
//save the settings before quitting
|
//save the settings before quitting
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ public class World{
|
|||||||
setSectorRules(sector);
|
setSectorRules(sector);
|
||||||
|
|
||||||
if(state.rules.defaultTeam.core() != null){
|
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<>();
|
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
|
||||||
ObjectSet<UnlockableContent> content = new ObjectSet<>();
|
ObjectSet<UnlockableContent> content = new ObjectSet<>();
|
||||||
|
|
||||||
float waterFloors = 0, totalFloors = 0;
|
|
||||||
|
|
||||||
for(Tile tile : world.tiles){
|
for(Tile tile : world.tiles){
|
||||||
if(world.getDarkness(tile.x, tile.y) >= 3){
|
if(world.getDarkness(tile.x, tile.y) >= 3){
|
||||||
continue;
|
continue;
|
||||||
@@ -280,10 +278,6 @@ public class World{
|
|||||||
if(liquid != null) content.add(liquid);
|
if(liquid != null) content.add(liquid);
|
||||||
|
|
||||||
if(!tile.block().isStatic()){
|
if(!tile.block().isStatic()){
|
||||||
totalFloors ++;
|
|
||||||
if(liquid == Liquids.water){
|
|
||||||
waterFloors += tile.floor().isDeep() ? 1f : 0.7f;
|
|
||||||
}
|
|
||||||
floorc.increment(tile.floor());
|
floorc.increment(tile.floor());
|
||||||
if(tile.overlay() != Blocks.air){
|
if(tile.overlay() != Blocks.air){
|
||||||
floorc.increment(tile.overlay());
|
floorc.increment(tile.overlay());
|
||||||
@@ -326,9 +320,9 @@ public class World{
|
|||||||
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
|
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.secinfo.resources = content.asArray();
|
sector.info.resources = content.asArray();
|
||||||
state.secinfo.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
sector.info.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||||
|
sector.saveInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context filterContext(Map map){
|
public Context filterContext(Map map){
|
||||||
|
|||||||
@@ -95,16 +95,17 @@ public abstract class UnlockableContent extends MappableContent{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean unlocked(){
|
public boolean unlocked(){
|
||||||
|
if(net.client()) return state.rules.researched.contains(name);
|
||||||
return unlocked || alwaysUnlocked;
|
return unlocked || alwaysUnlocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
||||||
public final boolean unlockedNow(){
|
public boolean unlockedNow(){
|
||||||
return unlocked || alwaysUnlocked || !state.isCampaign();
|
return unlocked() || !state.isCampaign();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean locked(){
|
public boolean locked(){
|
||||||
return !unlocked();
|
return !unlocked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class WaveGraph extends Table{
|
|||||||
int sum = 0;
|
int sum = 0;
|
||||||
|
|
||||||
for(SpawnGroup spawn : groups){
|
for(SpawnGroup spawn : groups){
|
||||||
int spawned = spawn.getUnitsSpawned(i);
|
int spawned = spawn.getSpawned(i);
|
||||||
values[index][spawn.type.id] += spawned;
|
values[index][spawn.type.id] += spawned;
|
||||||
if(spawned > 0){
|
if(spawned > 0){
|
||||||
used.add(spawn.type);
|
used.add(spawn.type);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class ForceFieldAbility extends Ability{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkRadius(Unit unit){
|
public void checkRadius(Unit unit){
|
||||||
//timer2 is used to store radius scale as an effect
|
//timer2 is used to store radius scale as an effect
|
||||||
realRad = radiusScale * radius;
|
realRad = radiusScale * radius;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,15 @@ public abstract class BulletType extends Content{
|
|||||||
this(1f, 1f);
|
this(1f, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return estimated damage per shot. this can be very inaccurate. */
|
||||||
|
public float estimateDPS(){
|
||||||
|
float sum = damage + splashDamage*0.75f;
|
||||||
|
if(fragBullet != null && fragBullet != this){
|
||||||
|
sum += fragBullet.estimateDPS() * fragBullets / 2f;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns maximum distance the bullet this bullet type has can travel. */
|
/** Returns maximum distance the bullet this bullet type has can travel. */
|
||||||
public float range(){
|
public float range(){
|
||||||
return Math.max(speed * lifetime * (1f - drag), range);
|
return Math.max(speed * lifetime * (1f - drag), range);
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ public class ContinuousLaserBulletType extends BulletType{
|
|||||||
this(0);
|
this(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float estimateDPS(){
|
||||||
|
//assume firing duration is about 100 by default, may not be accurate there's no way of knowing in this method
|
||||||
|
return damage * 100f / 5f;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float range(){
|
public float range(){
|
||||||
return length;
|
return length;
|
||||||
|
|||||||
@@ -766,9 +766,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawCracks(){
|
public void drawCracks(){
|
||||||
if(!damaged() || block.size > Block.maxCrackSize) return;
|
if(!damaged() || block.size > BlockRenderer.maxCrackSize) return;
|
||||||
int id = pos();
|
int id = pos();
|
||||||
TextureRegion region = Block.cracks[block.size - 1][Mathf.clamp((int)((1f - healthf()) * Block.crackRegions), 0, Block.crackRegions-1)];
|
TextureRegion region = renderer.blocks.cracks[block.size - 1][Mathf.clamp((int)((1f - healthf()) * BlockRenderer.crackRegions), 0, BlockRenderer.crackRegions-1)];
|
||||||
Draw.colorl(0.2f, 0.1f + (1f - healthf())* 0.6f);
|
Draw.colorl(0.2f, 0.1f + (1f - healthf())* 0.6f);
|
||||||
Draw.rect(region, x, y, (id%4)*90);
|
Draw.rect(region, x, y, (id%4)*90);
|
||||||
Draw.color();
|
Draw.color();
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
case rotation -> rotation;
|
case rotation -> rotation;
|
||||||
case health -> health;
|
case health -> health;
|
||||||
case maxHealth -> maxHealth;
|
case maxHealth -> maxHealth;
|
||||||
|
case ammo -> state.rules.unitAmmo ? type.ammoCapacity : ammo;
|
||||||
|
case ammoCapacity -> type.ammoCapacity;
|
||||||
case x -> x;
|
case x -> x;
|
||||||
case y -> y;
|
case y -> y;
|
||||||
case team -> team.id;
|
case team -> team.id;
|
||||||
@@ -284,7 +286,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
drag = type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f);
|
drag = type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f);
|
||||||
|
|
||||||
//apply knockback based on spawns
|
//apply knockback based on spawns
|
||||||
if(team != state.rules.waveTeam){
|
if(team != state.rules.waveTeam && state.rules.waves){
|
||||||
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
|
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
|
||||||
for(Tile spawn : spawner.getSpawns()){
|
for(Tile spawn : spawner.getSpawns()){
|
||||||
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
|
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
|
|||||||
Weapon weapon = mount.weapon;
|
Weapon weapon = mount.weapon;
|
||||||
|
|
||||||
float baseX = this.x, baseY = this.y;
|
float baseX = this.x, baseY = this.y;
|
||||||
boolean delay = weapon.firstShotDelay > 0f;
|
boolean delay = weapon.firstShotDelay + weapon.shotDelay > 0f;
|
||||||
|
|
||||||
(delay ? weapon.chargeSound : weapon.shootSound).at(x, y, Mathf.random(0.8f, 1.0f));
|
(delay ? weapon.chargeSound : weapon.shootSound).at(x, y, Mathf.random(0.8f, 1.0f));
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ public class DefaultWaves{
|
|||||||
begin = f;
|
begin = f;
|
||||||
end = f + next >= cap ? never : f + next;
|
end = f + next >= cap ? never : f + next;
|
||||||
max = 14;
|
max = 14;
|
||||||
unitScaling = rand.random(1f, 2f);
|
unitScaling = rand.random(1f, 3f);
|
||||||
shields = shieldAmount;
|
shields = shieldAmount;
|
||||||
shieldScaling = shieldsPerWave;
|
shieldScaling = shieldsPerWave;
|
||||||
spacing = space;
|
spacing = space;
|
||||||
@@ -329,7 +329,7 @@ public class DefaultWaves{
|
|||||||
|
|
||||||
while(step <= cap){
|
while(step <= cap){
|
||||||
createProgression.get(step);
|
createProgression.get(step);
|
||||||
step += (int)(rand.random(12, 25) * Mathf.lerp(1f, 0.4f, difficulty));
|
step += (int)(rand.random(13, 25) * Mathf.lerp(1f, 0.5f, difficulty));
|
||||||
}
|
}
|
||||||
|
|
||||||
int bossWave = (int)(rand.random(30, 60) * Mathf.lerp(1f, 0.7f, difficulty));
|
int bossWave = (int)(rand.random(30, 60) * Mathf.lerp(1f, 0.7f, difficulty));
|
||||||
|
|||||||
@@ -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 static class LaunchItemEvent{
|
||||||
public final ItemStack stack;
|
public final ItemStack stack;
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public class Objectives{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO fix
|
|
||||||
public static class SectorComplete extends SectorObjective{
|
public static class SectorComplete extends SectorObjective{
|
||||||
|
|
||||||
public SectorComplete(SectorPreset zone){
|
public SectorComplete(SectorPreset zone){
|
||||||
@@ -39,12 +38,12 @@ public class Objectives{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean complete(){
|
public boolean complete(){
|
||||||
return preset.sector.save != null && preset.sector.save.meta.wave >= preset.sector.save.meta.rules.winWave;
|
return preset.sector.save != null && preset.sector.save.meta.wave >= preset.captureWave;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String display(){
|
public String display(){
|
||||||
return Core.bundle.format("requirement.wave", preset.sector.save == null ? "<unknown>" : preset.sector.save.meta.rules.winWave, preset.localizedName);
|
return Core.bundle.format("requirement.capture", preset.localizedName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ public class Rules{
|
|||||||
public Seq<WeatherEntry> weather = new Seq<>(1);
|
public Seq<WeatherEntry> weather = new Seq<>(1);
|
||||||
/** Blocks that cannot be placed. */
|
/** Blocks that cannot be placed. */
|
||||||
public ObjectSet<Block> bannedBlocks = new ObjectSet<>();
|
public ObjectSet<Block> bannedBlocks = new ObjectSet<>();
|
||||||
|
/** Unlocked content names. Only used in multiplayer when the campaign is enabled. */
|
||||||
|
public ObjectSet<String> researched = new ObjectSet<>();
|
||||||
/** Whether ambient lighting is enabled. */
|
/** Whether ambient lighting is enabled. */
|
||||||
public boolean lighting = false;
|
public boolean lighting = false;
|
||||||
/** Whether enemy lighting is visible.
|
/** Whether enemy lighting is visible.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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.*;
|
||||||
@@ -25,7 +26,7 @@ public class SectorInfo{
|
|||||||
/** Export statistics. */
|
/** Export statistics. */
|
||||||
public ObjectMap<Item, ExportStat> export = new ObjectMap<>();
|
public ObjectMap<Item, ExportStat> export = new ObjectMap<>();
|
||||||
/** Items stored in all cores. */
|
/** Items stored in all cores. */
|
||||||
public ItemSeq coreItems = new ItemSeq();
|
public ItemSeq items = new ItemSeq();
|
||||||
/** The best available core type. */
|
/** The best available core type. */
|
||||||
public Block bestCoreType = Blocks.air;
|
public Block bestCoreType = Blocks.air;
|
||||||
/** Max storage capacity. */
|
/** Max storage capacity. */
|
||||||
@@ -38,8 +39,25 @@ public class SectorInfo{
|
|||||||
public @Nullable Sector destination;
|
public @Nullable Sector destination;
|
||||||
/** Resources known to occur at this sector. */
|
/** Resources known to occur at this sector. */
|
||||||
public Seq<UnlockableContent> resources = new Seq<>();
|
public Seq<UnlockableContent> resources = new Seq<>();
|
||||||
/** Time spent at this sector. Do not use unless you know what you're doing. */
|
/** Whether waves are enabled here. */
|
||||||
public transient float internalTimeSpent;
|
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;
|
||||||
|
|
||||||
/** Counter refresh state. */
|
/** Counter refresh state. */
|
||||||
private transient Interval time = new Interval();
|
private transient Interval time = new Interval();
|
||||||
@@ -79,26 +97,57 @@ public class SectorInfo{
|
|||||||
return export.get(item, ExportStat::new).mean;
|
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. */
|
/** Prepare data for writing to a save. */
|
||||||
public void prepare(){
|
public void prepare(){
|
||||||
//update core items
|
//update core items
|
||||||
coreItems.clear();
|
items.clear();
|
||||||
|
|
||||||
CoreBuild entity = state.rules.defaultTeam.core();
|
CoreBuild entity = state.rules.defaultTeam.core();
|
||||||
|
|
||||||
if(entity != null){
|
if(entity != null){
|
||||||
ItemModule items = entity.items;
|
ItemModule items = entity.items;
|
||||||
for(int i = 0; i < items.length(); i++){
|
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;
|
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;
|
||||||
|
|
||||||
//update sector's internal time spent counter
|
if(state.rules.sector != null){
|
||||||
state.rules.sector.setTimeSpent(internalTimeSpent);
|
state.rules.sector.info = this;
|
||||||
|
state.rules.sector.saveInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
SectorDamage.writeParameters(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update averages of various stats, updates some special sector logic.
|
/** Update averages of various stats, updates some special sector logic.
|
||||||
@@ -107,14 +156,6 @@ public class SectorInfo{
|
|||||||
//updating in multiplayer as a client doesn't make sense
|
//updating in multiplayer as a client doesn't make sense
|
||||||
if(net.client()) return;
|
if(net.client()) return;
|
||||||
|
|
||||||
internalTimeSpent += Time.delta;
|
|
||||||
|
|
||||||
//autorun turns
|
|
||||||
if(internalTimeSpent >= turnDuration){
|
|
||||||
internalTimeSpent = 0;
|
|
||||||
universe.runTurn();
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreBuild ent = state.rules.defaultTeam.core();
|
CoreBuild ent = state.rules.defaultTeam.core();
|
||||||
|
|
||||||
//refresh throughput
|
//refresh throughput
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ public class SpawnGroup implements Serializable{
|
|||||||
//serialization use only
|
//serialization use only
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the amount of units spawned on a specific wave. */
|
/** @return amount of units spawned on a specific wave. */
|
||||||
public int getUnitsSpawned(int wave){
|
public int getSpawned(int wave){
|
||||||
if(spacing == 0) spacing = 1;
|
if(spacing == 0) spacing = 1;
|
||||||
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
|
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
|
||||||
return 0;
|
return 0;
|
||||||
@@ -61,6 +61,11 @@ public class SpawnGroup implements Serializable{
|
|||||||
return Math.min(unitAmount + (int)(((wave - begin) / spacing) / unitScaling), max);
|
return Math.min(unitAmount + (int)(((wave - begin) / spacing) / unitScaling), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return amount of shields each unit has at a specific wave. */
|
||||||
|
public float getShield(int wave){
|
||||||
|
return Math.max(shields + shieldScaling*(wave - begin), 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a unit, and assigns correct values based on this group's data.
|
* Creates a unit, and assigns correct values based on this group's data.
|
||||||
* This method does not add() the unit.
|
* This method does not add() the unit.
|
||||||
@@ -76,7 +81,7 @@ public class SpawnGroup implements Serializable{
|
|||||||
unit.addItem(items.item, items.amount);
|
unit.addItem(items.item, items.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
unit.shield(Math.max(shields + shieldScaling*(wave - begin), 0));
|
unit.shield = getShield(wave);
|
||||||
|
|
||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class Stats{
|
|||||||
|
|
||||||
//weigh used fractions
|
//weigh used fractions
|
||||||
float frac = 0f;
|
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){
|
for(Item item : obtainable){
|
||||||
frac += Mathf.clamp((float)itemsDelivered.get(item, 0) / capacity) / (float)obtainable.size;
|
frac += Mathf.clamp((float)itemsDelivered.get(item, 0) / capacity) / (float)obtainable.size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ public class Team implements Comparable<Team>{
|
|||||||
Color.valueOf("ffd37f"), Color.valueOf("eab678"), Color.valueOf("d4816b")),
|
Color.valueOf("ffd37f"), Color.valueOf("eab678"), Color.valueOf("d4816b")),
|
||||||
crux = new Team(2, "crux", Color.valueOf("f25555"),
|
crux = new Team(2, "crux", Color.valueOf("f25555"),
|
||||||
Color.valueOf("fc8e6c"), Color.valueOf("f25555"), Color.valueOf("a04553")),
|
Color.valueOf("fc8e6c"), Color.valueOf("f25555"), Color.valueOf("a04553")),
|
||||||
green = new Team(3, "green", Color.valueOf("4dd98b")),
|
green = new Team(3, "green", Color.valueOf("54d67d")),
|
||||||
purple = new Team(4, "purple", Color.valueOf("9a4bdf")),
|
purple = new Team(4, "purple", Color.valueOf("995bb0")),
|
||||||
blue = new Team(5, "blue", Color.royal.cpy());
|
blue = new Team(5, "blue", Color.valueOf("5a4deb"));
|
||||||
|
|
||||||
static{
|
static{
|
||||||
Mathf.rand.setSeed(8);
|
Mathf.rand.setSeed(8);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
|
import mindustry.maps.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.world.blocks.storage.*;
|
import mindustry.world.blocks.storage.*;
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ public class Universe{
|
|||||||
private int netSeconds;
|
private int netSeconds;
|
||||||
private float secondCounter;
|
private float secondCounter;
|
||||||
private int turn;
|
private int turn;
|
||||||
|
private float turnCounter;
|
||||||
|
|
||||||
private Schematic lastLoadout;
|
private Schematic lastLoadout;
|
||||||
private ItemSeq lastLaunchResources = new ItemSeq();
|
private ItemSeq lastLaunchResources = new ItemSeq();
|
||||||
@@ -53,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.hasWaves() && s.hasBase() && !s.isBeingPlayed() && s.getSecondsPassed() > 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Update planet rotations, global time and relevant state. */
|
/** Update planet rotations, global time and relevant state. */
|
||||||
public void update(){
|
public void update(){
|
||||||
|
|
||||||
//only update time when not in multiplayer
|
//only update time when not in multiplayer
|
||||||
if(!net.client()){
|
if(!net.client()){
|
||||||
secondCounter += Time.delta / 60f;
|
secondCounter += Time.delta / 60f;
|
||||||
|
turnCounter += Time.delta;
|
||||||
|
|
||||||
|
//auto-run turns
|
||||||
|
if(turnCounter >= turnDuration){
|
||||||
|
turnCounter = 0;
|
||||||
|
runTurn();
|
||||||
|
}
|
||||||
|
|
||||||
if(secondCounter >= 1){
|
if(secondCounter >= 1){
|
||||||
seconds += (int)secondCounter;
|
seconds += (int)secondCounter;
|
||||||
@@ -132,42 +136,84 @@ public class Universe{
|
|||||||
//update relevant sectors
|
//update relevant sectors
|
||||||
for(Planet planet : content.planets()){
|
for(Planet planet : content.planets()){
|
||||||
for(Sector sector : planet.sectors){
|
for(Sector sector : planet.sectors){
|
||||||
if(sector.hasSave()){
|
if(sector.hasSave() && sector.hasBase()){
|
||||||
int spent = (int)(sector.getTimeSpent() / 60);
|
|
||||||
int actuallyPassed = Math.max(newSecondsPassed - spent, 0);
|
|
||||||
|
|
||||||
//increment seconds passed for this sector by the time that just passed with this turn
|
//increment seconds passed for this sector by the time that just passed with this turn
|
||||||
if(!sector.isBeingPlayed()){
|
if(!sector.isBeingPlayed()){
|
||||||
sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed);
|
//increment time
|
||||||
|
sector.info.secondsPassed += turnDuration/60f;
|
||||||
|
|
||||||
|
int wavesPassed = (int)(sector.info.secondsPassed*60f / sector.info.waveSpacing);
|
||||||
|
boolean attacked = sector.info.waves;
|
||||||
|
|
||||||
|
if(attacked){
|
||||||
|
sector.info.wavesPassed = wavesPassed;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
//TODO sector damage disabled for now
|
|
||||||
//check if the sector has been attacked too many times...
|
//check if the sector has been attacked too many times...
|
||||||
/*if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){
|
if(attacked && damage >= 0.999f){
|
||||||
//fire event for losing the sector
|
//fire event for losing the sector
|
||||||
Events.fire(new SectorLoseEvent(sector));
|
Events.fire(new SectorLoseEvent(sector));
|
||||||
|
|
||||||
//if so, just delete the save for now. it's lost.
|
//sector is dead.
|
||||||
//TODO don't delete it later maybe
|
sector.info.items.clear();
|
||||||
sector.save.delete();
|
sector.info.damage = 1f;
|
||||||
//clear recieved
|
sector.info.hasCore = false;
|
||||||
sector.setExtraItems(new ItemSeq());
|
sector.info.production.clear();
|
||||||
sector.save = null;
|
}else if(attacked && wavesPassed > 0 && sector.info.wave + wavesPassed >= sector.info.winWave && !sector.hasEnemyBase()){
|
||||||
}*/
|
//autocapture the sector
|
||||||
|
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
|
//queue random invasions
|
||||||
if(sector.save != null && sector.save.meta != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.destination != null){
|
if(!sector.isAttacked() && turn > invasionGracePeriod){
|
||||||
Sector to = sector.save.meta.secinfo.destination;
|
//TODO use factors like difficulty for better invasion chance
|
||||||
if(to.save != null){
|
if(sector.near().contains(Sector::hasEnemyBase) && Mathf.chance(baseInvasionChance)){
|
||||||
ItemSeq items = new ItemSeq();
|
int waveMax = Math.max(sector.info.winWave, sector.isBeingPlayed() ? state.wave : 0) + Mathf.random(2, 4) * 5;
|
||||||
//calculated exported items to this sector
|
float waveSpace = Math.max(sector.info.waveSpacing - Mathf.random(1, 4) * 5 * 60, 40 * 60);
|
||||||
sector.save.meta.secinfo.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed)));
|
|
||||||
to.addItems(items);
|
//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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +230,7 @@ public class Universe{
|
|||||||
for(Planet planet : content.planets()){
|
for(Planet planet : content.planets()){
|
||||||
for(Sector sector : planet.sectors){
|
for(Sector sector : planet.sectors){
|
||||||
if(sector.hasSave()){
|
if(sector.hasSave()){
|
||||||
count.add(sector.calculateItems());
|
count.add(sector.items());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ import static arc.Core.*;
|
|||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
public class BlockRenderer implements Disposable{
|
public class BlockRenderer implements Disposable{
|
||||||
|
public static final int crackRegions = 8, maxCrackSize = 9;
|
||||||
|
|
||||||
private static final int initialRequests = 32 * 32;
|
private static final int initialRequests = 32 * 32;
|
||||||
private static final int expandr = 9;
|
private static final int expandr = 9;
|
||||||
private static final Color shadowColor = new Color(0, 0, 0, 0.71f);
|
private static final Color shadowColor = new Color(0, 0, 0, 0.71f);
|
||||||
|
|
||||||
public final FloorRenderer floor = new FloorRenderer();
|
public final FloorRenderer floor = new FloorRenderer();
|
||||||
|
public TextureRegion[][] cracks;
|
||||||
|
|
||||||
private Seq<Tile> tileview = new Seq<>(false, initialRequests, Tile.class);
|
private Seq<Tile> tileview = new Seq<>(false, initialRequests, Tile.class);
|
||||||
private Seq<Tile> lightview = new Seq<>(false, initialRequests, Tile.class);
|
private Seq<Tile> lightview = new Seq<>(false, initialRequests, Tile.class);
|
||||||
@@ -40,6 +43,15 @@ public class BlockRenderer implements Disposable{
|
|||||||
|
|
||||||
public BlockRenderer(){
|
public BlockRenderer(){
|
||||||
|
|
||||||
|
Events.on(ClientLoadEvent.class, e -> {
|
||||||
|
cracks = new TextureRegion[maxCrackSize][crackRegions];
|
||||||
|
for(int size = 1; size <= maxCrackSize; size++){
|
||||||
|
for(int i = 0; i < crackRegions; i++){
|
||||||
|
cracks[size - 1][i] = Core.atlas.find("cracks-" + size + "-" + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Events.on(WorldLoadEvent.class, event -> {
|
Events.on(WorldLoadEvent.class, event -> {
|
||||||
shadowEvents.clear();
|
shadowEvents.clear();
|
||||||
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
|
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
|
||||||
|
|||||||
@@ -121,10 +121,12 @@ public class OverlayRenderer{
|
|||||||
Lines.stroke(2f);
|
Lines.stroke(2f);
|
||||||
Draw.color(Color.gray, Color.lightGray, Mathf.absin(Time.time(), 8f, 1f));
|
Draw.color(Color.gray, Color.lightGray, Mathf.absin(Time.time(), 8f, 1f));
|
||||||
|
|
||||||
for(Tile tile : spawner.getSpawns()){
|
if(state.rules.waves){
|
||||||
if(tile.within(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){
|
for(Tile tile : spawner.getSpawns()){
|
||||||
Draw.alpha(Mathf.clamp(1f - (player.dst(tile) - state.rules.dropZoneRadius) / spawnerMargin));
|
if(tile.within(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){
|
||||||
Lines.dashCircle(tile.worldx(), tile.worldy(), state.rules.dropZoneRadius);
|
Draw.alpha(Mathf.clamp(1f - (player.dst(tile) - state.rules.dropZoneRadius) / spawnerMargin));
|
||||||
|
Lines.dashCircle(tile.worldx(), tile.worldy(), state.rules.dropZoneRadius);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ public class SaveMeta{
|
|||||||
public Map map;
|
public Map map;
|
||||||
public int wave;
|
public int wave;
|
||||||
public Rules rules;
|
public Rules rules;
|
||||||
public SectorInfo secinfo;
|
|
||||||
public StringMap tags;
|
public StringMap tags;
|
||||||
public String[] mods;
|
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.version = version;
|
||||||
this.build = build;
|
this.build = build;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
@@ -29,8 +27,5 @@ public class SaveMeta{
|
|||||||
this.rules = rules;
|
this.rules = rules;
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
|
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.get("mapname"),
|
||||||
map.getInt("wave"),
|
map.getInt("wave"),
|
||||||
JsonIO.read(Rules.class, map.get("rules", "{}")),
|
JsonIO.read(Rules.class, map.get("rules", "{}")),
|
||||||
JsonIO.read(SectorInfo.class, map.get("secinfo", "{}")),
|
|
||||||
map
|
map
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -74,6 +73,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
//prepare campaign data for writing
|
//prepare campaign data for writing
|
||||||
if(state.isCampaign()){
|
if(state.isCampaign()){
|
||||||
state.secinfo.prepare();
|
state.secinfo.prepare();
|
||||||
|
state.rules.sector.saveInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
//flush tech node progress
|
//flush tech node progress
|
||||||
@@ -89,7 +89,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
"wave", state.wave,
|
"wave", state.wave,
|
||||||
"wavetime", state.wavetime,
|
"wavetime", state.wavetime,
|
||||||
"stats", JsonIO.write(state.stats),
|
"stats", JsonIO.write(state.stats),
|
||||||
"secinfo", state.isCampaign() ? JsonIO.write(state.secinfo) : "{}",
|
|
||||||
"rules", JsonIO.write(state.rules),
|
"rules", JsonIO.write(state.rules),
|
||||||
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
||||||
"width", world.width(),
|
"width", world.width(),
|
||||||
@@ -107,14 +106,13 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
state.wave = map.getInt("wave");
|
state.wave = map.getInt("wave");
|
||||||
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
|
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
|
||||||
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
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", "{}"));
|
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
||||||
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
|
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
|
||||||
lastReadBuild = map.getInt("build", -1);
|
lastReadBuild = map.getInt("build", -1);
|
||||||
|
|
||||||
//load time spent on sector into state
|
//load in sector info
|
||||||
if(state.rules.sector != null){
|
if(state.rules.sector != null){
|
||||||
state.secinfo.internalTimeSpent = state.rules.sector.getStoredTimeSpent();
|
state.secinfo = state.rules.sector.info;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!headless){
|
if(!headless){
|
||||||
|
|||||||
@@ -450,6 +450,16 @@ public class TypeIO{
|
|||||||
return color.set(read.i());
|
return color.set(read.i());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeContent(Writes write, Content cont){
|
||||||
|
write.b(cont.getContentType().ordinal());
|
||||||
|
write.s(cont.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Content readContent(Reads read){
|
||||||
|
byte id = read.b();
|
||||||
|
return content.getByID(ContentType.all[id], read.s());
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeLiquid(Writes write, Liquid liquid){
|
public static void writeLiquid(Writes write, Liquid liquid){
|
||||||
write.s(liquid == null ? -1 : liquid.id);
|
write.s(liquid == null ? -1 : liquid.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public enum LAccess{
|
|||||||
powerNetCapacity,
|
powerNetCapacity,
|
||||||
powerNetIn,
|
powerNetIn,
|
||||||
powerNetOut,
|
powerNetOut,
|
||||||
|
ammo,
|
||||||
|
ammoCapacity,
|
||||||
health,
|
health,
|
||||||
maxHealth,
|
maxHealth,
|
||||||
heat,
|
heat,
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ public class LAssembler{
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
double value = Double.parseDouble(symbol);
|
double value = Double.parseDouble(symbol);
|
||||||
|
if(Double.isNaN(value) || Double.isInfinite(value)) value = 0;
|
||||||
//this creates a hidden const variable with the specified value
|
//this creates a hidden const variable with the specified value
|
||||||
String key = "___" + value;
|
String key = "___" + value;
|
||||||
return putConst(key, value).id;
|
return putConst(key, value).id;
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ public class LCanvas extends Table{
|
|||||||
statements.finishLayout();
|
statements.finishLayout();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).growX();
|
}).growX().height(38);
|
||||||
|
|
||||||
row();
|
row();
|
||||||
|
|
||||||
|
|||||||
@@ -110,12 +110,12 @@ public class LExecutor{
|
|||||||
|
|
||||||
public double num(int index){
|
public double num(int index){
|
||||||
Var v = vars[index];
|
Var v = vars[index];
|
||||||
return v.isobj ? v.objval != null ? 1 : 0 : v.numval;
|
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : v.numval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float numf(int index){
|
public float numf(int index){
|
||||||
Var v = vars[index];
|
Var v = vars[index];
|
||||||
return v.isobj ? v.objval != null ? 1 : 0 : (float)v.numval;
|
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : (float)v.numval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int numi(int index){
|
public int numi(int index){
|
||||||
@@ -129,7 +129,7 @@ public class LExecutor{
|
|||||||
public void setnum(int index, double value){
|
public void setnum(int index, double value){
|
||||||
Var v = vars[index];
|
Var v = vars[index];
|
||||||
if(v.constant) return;
|
if(v.constant) return;
|
||||||
v.numval = value;
|
v.numval = Double.isNaN(value) || Double.isInfinite(value) ? 0 : value;
|
||||||
v.objval = null;
|
v.objval = null;
|
||||||
v.isobj = false;
|
v.isobj = false;
|
||||||
}
|
}
|
||||||
@@ -737,7 +737,7 @@ public class LExecutor{
|
|||||||
v.objval = f.objval;
|
v.objval = f.objval;
|
||||||
v.isobj = true;
|
v.isobj = true;
|
||||||
}else{
|
}else{
|
||||||
v.numval = f.numval;
|
v.numval = Double.isNaN(f.numval) || Double.isInfinite(f.numval) ? 0 : f.numval;
|
||||||
v.isobj = false;
|
v.isobj = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,18 @@ package mindustry.maps;
|
|||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
|
import arc.util.*;
|
||||||
import mindustry.ai.*;
|
import mindustry.ai.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
|
import mindustry.entities.abilities.*;
|
||||||
|
import mindustry.game.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
|
import mindustry.logic.*;
|
||||||
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.*;
|
||||||
@@ -15,6 +22,286 @@ import static mindustry.Vars.*;
|
|||||||
public class SectorDamage{
|
public class SectorDamage{
|
||||||
//direct damage is for testing only
|
//direct damage is for testing only
|
||||||
private static final boolean direct = false, rubble = true;
|
private static final boolean direct = false, rubble = true;
|
||||||
|
private static final int maxWavesSimulated = 50;
|
||||||
|
|
||||||
|
/** @return calculated capture progress of the enemy */
|
||||||
|
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.
|
||||||
|
if(wavesPassed > 0){
|
||||||
|
int waveBegin = wave;
|
||||||
|
int waveEnd = wave + wavesPassed;
|
||||||
|
|
||||||
|
//do not simulate every single wave if there's too many
|
||||||
|
if(wavesPassed > maxWavesSimulated){
|
||||||
|
waveBegin = waveEnd - maxWavesSimulated;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = waveBegin; i <= waveEnd; i++){
|
||||||
|
|
||||||
|
float efficiency = health / info.sumHealth;
|
||||||
|
float dps = info.sumDps * efficiency;
|
||||||
|
float rps = info.sumRps * efficiency;
|
||||||
|
|
||||||
|
float enemyDps = info.waveDpsBase + info.waveDpsSlope * (i);
|
||||||
|
float enemyHealth = info.waveHealthBase + info.waveHealthSlope * (i);
|
||||||
|
|
||||||
|
//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
|
||||||
|
|
||||||
|
//sector is lost, enemy took too long.
|
||||||
|
if(timeDestroyEnemy > timeDestroyBase){
|
||||||
|
health = 0f;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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);
|
||||||
|
|
||||||
|
//apply damage to units
|
||||||
|
float unitDamage = damage * state.secinfo.sumHealth;
|
||||||
|
Tile spawn = spawner.getFirstSpawn();
|
||||||
|
|
||||||
|
//damage only units near the spawn point
|
||||||
|
if(spawn != null){
|
||||||
|
Seq<Unit> allies = new Seq<>();
|
||||||
|
for(Unit ally : Groups.unit){
|
||||||
|
if(ally.team == state.rules.defaultTeam && ally.within(spawn, state.rules.dropZoneRadius * 2.5f)){
|
||||||
|
allies.add(ally);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allies.sort(u -> u.dst2(spawn));
|
||||||
|
|
||||||
|
//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.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculates damage simulation parameters before a game is saved. */
|
||||||
|
public static void writeParameters(SectorInfo 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;
|
||||||
|
|
||||||
|
Tile start = spawns.first();
|
||||||
|
|
||||||
|
Time.mark();
|
||||||
|
var field = pathfinder.getField(state.rules.waveTeam, Pathfinder.costGround, Pathfinder.fieldCore);
|
||||||
|
Seq<Tile> path = new Seq<>();
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
if(field != null && field.weights != null){
|
||||||
|
int[][] weights = field.weights;
|
||||||
|
int count = 0;
|
||||||
|
Tile current = start;
|
||||||
|
while(count < world.width() * world.height()){
|
||||||
|
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;
|
||||||
|
|
||||||
|
Tile other = world.tile(nx, ny);
|
||||||
|
if(other != null && weights[nx][ny] < minCost && weights[nx][ny] != -1){
|
||||||
|
minCost = weights[nx][ny];
|
||||||
|
current = other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.add(current);
|
||||||
|
|
||||||
|
if(current.build == core){
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
count ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found){
|
||||||
|
path = Astar.pathfind(start, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
//create sparse tile array for fast range query
|
||||||
|
int sparseSkip = 6;
|
||||||
|
//TODO if this is slow, use a quadtree
|
||||||
|
Seq<Tile> sparse = new Seq<>(path.size / sparseSkip + 1);
|
||||||
|
|
||||||
|
for(int i = 0; i < path.size; i++){
|
||||||
|
if(i % sparseSkip == 0){
|
||||||
|
sparse.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
|
||||||
|
|
||||||
|
for(Tile t : path){
|
||||||
|
int radius = 2;
|
||||||
|
|
||||||
|
//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){
|
||||||
|
//health is divided by block size, because multiblocks are counted multiple times.
|
||||||
|
sumHealth += tile.build.health / tile.block().size;
|
||||||
|
totalPathBuild += 1f / tile.block().size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float avgHealth = totalPathBuild <= 1 ? sumHealth : sumHealth / totalPathBuild;
|
||||||
|
|
||||||
|
//block dps + regen + extra health/shields
|
||||||
|
for(Building build : Groups.build){
|
||||||
|
float e = build.efficiency();
|
||||||
|
if(e > 0.08f){
|
||||||
|
if(build.team == state.rules.defaultTeam && build instanceof Ranged ranged && sparse.contains(t -> t.within(build, ranged.range()))){
|
||||||
|
if(build.block instanceof Turret t && build instanceof TurretBuild b && b.hasAmmo()){
|
||||||
|
sumDps += t.shots / t.reloadTime * 60f * b.peekAmmo().estimateDPS() * e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(build.block instanceof MendProjector m){
|
||||||
|
sumRps += m.healPercent / m.reload * avgHealth * 60f / 100f * e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(build.block instanceof ForceProjector f){
|
||||||
|
sumHealth += f.breakage * e;
|
||||||
|
sumRps += 1f * e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float curEnemyHealth = 0f, curEnemyDps = 0f;
|
||||||
|
|
||||||
|
//unit regen + health + dps
|
||||||
|
for(Unit unit : Groups.unit){
|
||||||
|
//skip player
|
||||||
|
if(unit.isPlayer()) continue;
|
||||||
|
|
||||||
|
if(unit.team == state.rules.defaultTeam){
|
||||||
|
sumHealth += unit.health + unit.shield;
|
||||||
|
sumDps += unit.type().dpsEstimate;
|
||||||
|
if(unit.abilities.find(a -> a instanceof HealFieldAbility) instanceof HealFieldAbility h){
|
||||||
|
sumRps += h.amount / h.reload * 60f;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
curEnemyDps += unit.type().dpsEstimate;
|
||||||
|
curEnemyHealth += unit.health;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//calculate DPS and health for the next few waves and store in list
|
||||||
|
var reg = new LinearRegression();
|
||||||
|
Seq<Vec2> waveDps = new Seq<>(), waveHealth = new Seq<>();
|
||||||
|
|
||||||
|
for(int wave = state.wave, i = 0; i < 3; wave += (1 + i++)){
|
||||||
|
float sumWaveDps = 0f, sumWaveHealth = 0f;
|
||||||
|
|
||||||
|
//first wave has to take into account current dps
|
||||||
|
if(wave == state.wave){
|
||||||
|
sumWaveDps += curEnemyDps;
|
||||||
|
sumWaveHealth += curEnemyHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(SpawnGroup group : state.rules.spawns){
|
||||||
|
int spawned = group.getSpawned(wave);
|
||||||
|
if(spawned <= 0) continue;
|
||||||
|
sumWaveHealth += spawned * (group.getShield(wave) + group.type.health);
|
||||||
|
sumWaveDps += spawned * group.type.dpsEstimate;
|
||||||
|
}
|
||||||
|
waveDps.add(new Vec2(wave, sumWaveDps));
|
||||||
|
waveHealth.add(new Vec2(wave, sumWaveHealth));
|
||||||
|
}
|
||||||
|
|
||||||
|
//calculate linear regression of the wave data and store it
|
||||||
|
reg.calculate(waveHealth);
|
||||||
|
info.waveHealthBase = reg.intercept;
|
||||||
|
info.waveHealthSlope = reg.slope;
|
||||||
|
|
||||||
|
reg.calculate(waveDps);
|
||||||
|
info.waveDpsBase = reg.intercept;
|
||||||
|
info.waveDpsSlope = reg.slope;
|
||||||
|
|
||||||
|
info.sumHealth = sumHealth;
|
||||||
|
info.sumDps = sumDps;
|
||||||
|
info.sumRps = sumRps;
|
||||||
|
|
||||||
|
//finally, find an equation to put it all together and produce a 0-1 number
|
||||||
|
//due to the way most defenses are structured, this number will likely need a ^4 power or so
|
||||||
|
}
|
||||||
|
|
||||||
public static void apply(float fraction){
|
public static void apply(float fraction){
|
||||||
Tiles tiles = world.tiles;
|
Tiles tiles = world.tiles;
|
||||||
@@ -35,23 +322,70 @@ public class SectorDamage{
|
|||||||
if(core != null && !frontier.isEmpty()){
|
if(core != null && !frontier.isEmpty()){
|
||||||
for(Tile spawner : frontier){
|
for(Tile spawner : frontier){
|
||||||
//find path from spawn to core
|
//find path from spawn to core
|
||||||
//TODO this is broken
|
|
||||||
Seq<Tile> path = Astar.pathfind(spawner, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid()));
|
Seq<Tile> path = Astar.pathfind(spawner, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid()));
|
||||||
int amount = (int)(path.size * fraction);
|
Seq<Building> removal = new Seq<>();
|
||||||
for(int i = 0; i < amount; i++){
|
|
||||||
Tile t = path.get(i);
|
|
||||||
Geometry.circle(t.x, t.y, tiles.width, tiles.height, 5, (cx, cy) -> {
|
|
||||||
Tile other = tiles.getn(cx, cy);
|
|
||||||
//just remove all the buildings in the way - as long as they're not cores!
|
|
||||||
if(other.build != null && other.team() == state.rules.defaultTeam && !(other.block() instanceof CoreBlock)){
|
|
||||||
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
|
|
||||||
Effect.rubble(other.build.x, other.build.y, other.block().size);
|
|
||||||
}
|
|
||||||
|
|
||||||
other.remove();
|
int radius = 3;
|
||||||
|
|
||||||
|
//only penetrate a certain % by health, not by distance
|
||||||
|
float totalHealth = path.sumf(t -> {
|
||||||
|
float s = 0;
|
||||||
|
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() && Mathf.within(dx, dy, radius)){
|
||||||
|
Tile other = world.rawTile(wx, wy);
|
||||||
|
s += other.team() == state.rules.defaultTeam ? other.build.health / other.block().size : 0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
float targetHealth = totalHealth * fraction;
|
||||||
|
float healthCount = 0;
|
||||||
|
|
||||||
|
out:
|
||||||
|
for(int i = 0; i < path.size && healthCount < targetHealth; i++){
|
||||||
|
Tile t = path.get(i);
|
||||||
|
|
||||||
|
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() && Mathf.within(dx, dy, radius)){
|
||||||
|
Tile other = world.rawTile(wx, wy);
|
||||||
|
|
||||||
|
//just remove all the buildings in the way - as long as they're not cores
|
||||||
|
if(other.build != null && other.team() == state.rules.defaultTeam && !(other.block() instanceof CoreBlock)){
|
||||||
|
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
|
||||||
|
Effect.rubble(other.build.x, other.build.y, other.block().size);
|
||||||
|
}
|
||||||
|
|
||||||
|
//since the whole block is removed, count the whole health
|
||||||
|
healthCount += other.build.health;
|
||||||
|
|
||||||
|
removal.add(other.build);
|
||||||
|
|
||||||
|
if(healthCount >= targetHealth){
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(Building r : removal){
|
||||||
|
if(r.tile.build == r){
|
||||||
|
r.tile.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//kill every core if damage is maximum
|
||||||
|
if(fraction >= 1){
|
||||||
|
for(Building c : state.rules.defaultTeam.cores().copy()){
|
||||||
|
c.tile.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//make sure the main class exists before loading it; if it doesn't just don't put it there
|
//make sure the main class exists before loading it; if it doesn't just don't put it there
|
||||||
if(mainFile.exists()){
|
if(mainFile.exists() && Core.settings.getBool("mod-" + meta.name.toLowerCase().replace(" ", "-") + "-enabled", true)){
|
||||||
//mobile versions don't support class mods
|
//mobile versions don't support class mods
|
||||||
if(ios){
|
if(ios){
|
||||||
throw new IllegalArgumentException("Java class mods are not supported on iOS.");
|
throw new IllegalArgumentException("Java class mods are not supported on iOS.");
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package mindustry.net;
|
|||||||
import arc.*;
|
import arc.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import arc.util.io.*;
|
import arc.util.io.*;
|
||||||
|
import mindustry.content.*;
|
||||||
import mindustry.core.*;
|
import mindustry.core.*;
|
||||||
|
import mindustry.ctype.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.io.*;
|
import mindustry.io.*;
|
||||||
@@ -21,6 +23,18 @@ public class NetworkIO{
|
|||||||
public static void writeWorld(Player player, OutputStream os){
|
public static void writeWorld(Player player, OutputStream os){
|
||||||
|
|
||||||
try(DataOutputStream stream = new DataOutputStream(os)){
|
try(DataOutputStream stream = new DataOutputStream(os)){
|
||||||
|
//write all researched content to rules if hosting
|
||||||
|
if(state.isCampaign()){
|
||||||
|
state.rules.researched.clear();
|
||||||
|
for(ContentType type : ContentType.all){
|
||||||
|
for(Content c : content.getBy(type)){
|
||||||
|
if(c instanceof UnlockableContent u && u.unlocked() && TechTree.get(u) != null){
|
||||||
|
state.rules.researched.add(u.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stream.writeUTF(JsonIO.write(state.rules));
|
stream.writeUTF(JsonIO.write(state.rules));
|
||||||
SaveIO.getSaveWriter().writeStringMap(stream, state.map.tags);
|
SaveIO.getSaveWriter().writeStringMap(stream, state.map.tags);
|
||||||
|
|
||||||
@@ -44,6 +58,8 @@ public class NetworkIO{
|
|||||||
state.rules = JsonIO.read(Rules.class, stream.readUTF());
|
state.rules = JsonIO.read(Rules.class, stream.readUTF());
|
||||||
state.map = new Map(SaveIO.getSaveWriter().readStringMap(stream));
|
state.map = new Map(SaveIO.getSaveWriter().readStringMap(stream));
|
||||||
|
|
||||||
|
Log.info("READ RULES: @", state.rules.researched);
|
||||||
|
|
||||||
state.wave = stream.readInt();
|
state.wave = stream.readInt();
|
||||||
state.wavetime = stream.readFloat();
|
state.wavetime = stream.readFloat();
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public class Planet extends UnlockableContent{
|
|||||||
public void updateBaseCoverage(){
|
public void updateBaseCoverage(){
|
||||||
for(Sector sector : sectors){
|
for(Sector sector : sectors){
|
||||||
float sum = 1f;
|
float sum = 1f;
|
||||||
for(Sector other : sector.inRange(2)){
|
for(Sector other : sector.near()){
|
||||||
if(other.generateEnemyBase){
|
if(other.generateEnemyBase){
|
||||||
sum += 1f;
|
sum += 1f;
|
||||||
}
|
}
|
||||||
@@ -204,6 +204,10 @@ public class Planet extends UnlockableContent{
|
|||||||
@Override
|
@Override
|
||||||
public void init(){
|
public void init(){
|
||||||
|
|
||||||
|
for(Sector sector : sectors){
|
||||||
|
sector.loadInfo();
|
||||||
|
}
|
||||||
|
|
||||||
if(generator != null){
|
if(generator != null){
|
||||||
Noise.setSeed(id + 1);
|
Noise.setSeed(id + 1);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.game.Saves.*;
|
import mindustry.game.Saves.*;
|
||||||
|
import mindustry.game.*;
|
||||||
import mindustry.graphics.g3d.PlanetGrid.*;
|
import mindustry.graphics.g3d.PlanetGrid.*;
|
||||||
import mindustry.world.modules.*;
|
import mindustry.world.modules.*;
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ public class Sector{
|
|||||||
|
|
||||||
public @Nullable SaveSlot save;
|
public @Nullable SaveSlot save;
|
||||||
public @Nullable SectorPreset preset;
|
public @Nullable SectorPreset preset;
|
||||||
|
public SectorInfo info = new SectorInfo();
|
||||||
|
|
||||||
/** Number 0-1 indicating the difficulty based on nearby bases. */
|
/** Number 0-1 indicating the difficulty based on nearby bases. */
|
||||||
public float baseCoverage;
|
public float baseCoverage;
|
||||||
@@ -38,60 +40,50 @@ public class Sector{
|
|||||||
this.id = tile.id;
|
this.id = tile.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Seq<Sector> inRange(int range){
|
public Seq<Sector> near(){
|
||||||
//TODO cleanup/remove
|
|
||||||
if(true){
|
|
||||||
tmpSeq1.clear();
|
|
||||||
neighbors(tmpSeq1::add);
|
|
||||||
|
|
||||||
return tmpSeq1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpSeq1.clear();
|
tmpSeq1.clear();
|
||||||
tmpSeq2.clear();
|
near(tmpSeq1::add);
|
||||||
tmpSet.clear();
|
|
||||||
|
|
||||||
tmpSeq1.add(this);
|
return tmpSeq1;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void neighbors(Cons<Sector> cons){
|
public void near(Cons<Sector> cons){
|
||||||
for(Ptile tile : tile.tiles){
|
for(Ptile tile : tile.tiles){
|
||||||
cons.get(planet.getSector(tile));
|
cons.get(planet.getSector(tile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return whether this sector can be landed on at all.
|
/** @return whether this sector can be landed on at all.
|
||||||
* Only sectors adjacent to non-wave sectors can be landed on.
|
* Only sectors adjacent to non-wave sectors can be landed on. */
|
||||||
* TODO also preset sectors*/
|
|
||||||
public boolean unlocked(){
|
public boolean unlocked(){
|
||||||
return hasBase() || (preset != null && preset.alwaysUnlocked);
|
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. */
|
/** @return whether the player has a base here. */
|
||||||
public boolean hasBase(){
|
public boolean hasBase(){
|
||||||
return save != null && !save.meta.tags.getBool("nocores");
|
return save != null && info.hasCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @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 && (save == null || save.meta.rules.waves);
|
return generateEnemyBase && (save == null || info.waves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBeingPlayed(){
|
public boolean isBeingPlayed(){
|
||||||
@@ -99,13 +91,18 @@ public class Sector{
|
|||||||
return Vars.state.isGame() && Vars.state.rules.sector == this && !Vars.state.gameOver;
|
return Vars.state.isGame() && Vars.state.rules.sector == this && !Vars.state.gameOver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCaptured(){
|
public String name(){
|
||||||
return save != null && !save.meta.rules.waves;
|
if(preset != null) return preset.localizedName;
|
||||||
|
return info.name == null ? id + "" : info.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return whether waves are present - if true, any bases here will be attacked. */
|
public void setName(String name){
|
||||||
public boolean hasWaves(){
|
info.name = name;
|
||||||
return save != null && save.meta.rules.waves;
|
saveInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCaptured(){
|
||||||
|
return save != null && !info.waves;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSave(){
|
public boolean hasSave(){
|
||||||
@@ -130,15 +127,6 @@ public class Sector{
|
|||||||
return res % 2 == 0 ? res : res + 1;
|
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){
|
public void addItem(Item item, int amount){
|
||||||
removeItem(item, -amount);
|
removeItem(item, -amount);
|
||||||
}
|
}
|
||||||
@@ -156,137 +144,27 @@ public class Sector{
|
|||||||
int cap = state.rules.defaultTeam.core().storageCapacity;
|
int cap = state.rules.defaultTeam.core().storageCapacity;
|
||||||
items.each((item, amount) -> storage.add(item, Math.min(cap - storage.get(item), amount)));
|
items.each((item, amount) -> storage.add(item, Math.min(cap - storage.get(item), amount)));
|
||||||
}
|
}
|
||||||
}else{
|
}else if(hasBase()){
|
||||||
ItemSeq recv = getExtraItems();
|
items.each((item, amount) -> info.items.add(item, Math.min(info.storageCapacity - info.items.get(item), amount)));
|
||||||
|
saveInfo();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemSeq calculateItems(){
|
/** @return items currently in this sector, taking into account playing state. */
|
||||||
|
public ItemSeq items(){
|
||||||
ItemSeq count = new ItemSeq();
|
ItemSeq count = new ItemSeq();
|
||||||
|
|
||||||
//for sectors being played on, add items directly
|
//for sectors being played on, add items directly
|
||||||
if(isBeingPlayed()){
|
if(isBeingPlayed()){
|
||||||
count.add(state.rules.defaultTeam.items());
|
count.add(state.rules.defaultTeam.items());
|
||||||
}else if(save != null){
|
}else{
|
||||||
//add items already present
|
//add items already present
|
||||||
count.add(save.meta.secinfo.coreItems);
|
count.add(info.items);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemSeq calculateReceivedItems(){
|
|
||||||
ItemSeq count = new ItemSeq();
|
|
||||||
|
|
||||||
if(save != null){
|
|
||||||
long seconds = getSecondsPassed();
|
|
||||||
|
|
||||||
//add produced items
|
|
||||||
save.meta.secinfo.production.each((item, stat) -> count.add(item, (int)(stat.mean * seconds)));
|
|
||||||
|
|
||||||
//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 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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String key(String key){
|
|
||||||
return planet.name + "-s-" + id + "-" + key;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void put(String key, Object value){
|
|
||||||
Core.settings.put(key(key), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return planet.name + "#" + id;
|
return planet.name + "#" + id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ public class UnitType extends UnlockableContent{
|
|||||||
public int mineTier = -1;
|
public int mineTier = -1;
|
||||||
public float buildSpeed = 1f, mineSpeed = 1f;
|
public float buildSpeed = 1f, mineSpeed = 1f;
|
||||||
|
|
||||||
|
/** This is a VERY ROUGH estimate of unit DPS. */
|
||||||
|
public float dpsEstimate = -1;
|
||||||
public float clipSize = -1;
|
public float clipSize = -1;
|
||||||
public boolean canDrown = true;
|
public boolean canDrown = true;
|
||||||
public float engineOffset = 5f, engineSize = 2.5f;
|
public float engineOffset = 5f, engineSize = 2.5f;
|
||||||
@@ -266,6 +268,17 @@ public class UnitType extends UnlockableContent{
|
|||||||
|
|
||||||
ammoCapacity = Math.max(1, (int)(shotsPerSecond * targetSeconds));
|
ammoCapacity = Math.max(1, (int)(shotsPerSecond * targetSeconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//calculate estimated DPS for one target based on weapons
|
||||||
|
if(dpsEstimate < 0){
|
||||||
|
dpsEstimate = weapons.sumf(w -> (w.bullet.estimateDPS() / w.reload) * w.shots * 60f);
|
||||||
|
|
||||||
|
//suicide enemy
|
||||||
|
if(weapons.contains(w -> w.bullet.killShooter)){
|
||||||
|
//scale down DPS to be insignificant
|
||||||
|
dpsEstimate /= 100f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@@ -436,7 +449,7 @@ public class UnitType extends UnlockableContent{
|
|||||||
applyColor(unit);
|
applyColor(unit);
|
||||||
|
|
||||||
//draw back items
|
//draw back items
|
||||||
if(unit.hasItem() && unit.itemTime > 0.01f){
|
if(unit.item() != null && unit.itemTime > 0.01f){
|
||||||
float size = (itemSize + Mathf.absin(Time.time(), 5f, 1f)) * unit.itemTime;
|
float size = (itemSize + Mathf.absin(Time.time(), 5f, 1f)) * unit.itemTime;
|
||||||
|
|
||||||
Draw.mixcol(Pal.accent, Mathf.absin(Time.time(), 5f, 0.5f));
|
Draw.mixcol(Pal.accent, Mathf.absin(Time.time(), 5f, 0.5f));
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import arc.func.*;
|
|||||||
public class IntFormat{
|
public class IntFormat{
|
||||||
private final StringBuilder builder = new StringBuilder();
|
private final StringBuilder builder = new StringBuilder();
|
||||||
private final String text;
|
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;
|
private Func<Integer, String> converter = String::valueOf;
|
||||||
|
|
||||||
public IntFormat(String text){
|
public IntFormat(String text){
|
||||||
@@ -30,4 +30,14 @@ public class IntFormat{
|
|||||||
lastValue = value;
|
lastValue = value;
|
||||||
return builder;
|
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
|
@StyleDefaults
|
||||||
public class Styles{
|
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 Drawable black, black9, black8, black6, black3, black5, none, flatDown, flatOver;
|
||||||
public static ButtonStyle defaultb, waveb;
|
public static ButtonStyle defaultb, waveb;
|
||||||
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
|
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
|
||||||
|
|||||||
@@ -76,13 +76,15 @@ public class HostDialog extends BaseDialog{
|
|||||||
platform.updateLobby();
|
platform.updateLobby();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if(Version.modifier.contains("beta") || Version.modifier.contains("alpha")){
|
||||||
|
Core.settings.put("publichost", false);
|
||||||
|
platform.updateLobby();
|
||||||
|
Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("@public.beta"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Version.modifier.contains("beta")){
|
|
||||||
Core.settings.put("publichost", false);
|
|
||||||
platform.updateLobby();
|
|
||||||
Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("@public.beta"));
|
|
||||||
}
|
|
||||||
}catch(IOException e){
|
}catch(IOException e){
|
||||||
ui.showException("@server.error", e);
|
ui.showException("@server.error", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -367,8 +367,10 @@ public class JoinDialog extends BaseDialog{
|
|||||||
|
|
||||||
local.row();
|
local.row();
|
||||||
|
|
||||||
TextButton button = local.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version))
|
TextButton button = local.button("", Styles.cleart, () -> {
|
||||||
.width(w).pad(5f).get();
|
Events.fire(new ClientPreConnectEvent(host));
|
||||||
|
safeConnect(host.address, host.port, host.version);
|
||||||
|
}).width(w).pad(5f).get();
|
||||||
button.clearChildren();
|
button.clearChildren();
|
||||||
buildServer(host, button);
|
buildServer(host, button);
|
||||||
}
|
}
|
||||||
@@ -379,8 +381,10 @@ public class JoinDialog extends BaseDialog{
|
|||||||
|
|
||||||
global.row();
|
global.row();
|
||||||
|
|
||||||
TextButton button = global.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version))
|
TextButton button = global.button("", Styles.cleart, () -> {
|
||||||
.width(w).pad(5f).get();
|
Events.fire(new ClientPreConnectEvent(host));
|
||||||
|
safeConnect(host.address, host.port, host.version);
|
||||||
|
}).width(w).pad(5f).get();
|
||||||
button.clearChildren();
|
button.clearChildren();
|
||||||
buildServer(host, button);
|
buildServer(host, button);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package mindustry.ui.dialogs;
|
|||||||
|
|
||||||
import arc.*;
|
import arc.*;
|
||||||
import arc.func.*;
|
import arc.func.*;
|
||||||
|
import arc.input.*;
|
||||||
import arc.scene.ui.*;
|
import arc.scene.ui.*;
|
||||||
import arc.scene.ui.layout.*;
|
import arc.scene.ui.layout.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
@@ -33,7 +34,14 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
|||||||
cont.clear();
|
cont.clear();
|
||||||
buttons.clear();
|
buttons.clear();
|
||||||
|
|
||||||
addCloseButton();
|
buttons.defaults().size(160f, 64f);
|
||||||
|
buttons.button("@back", Icon.left, this::hide);
|
||||||
|
|
||||||
|
keyDown(key -> {
|
||||||
|
if(key == KeyCode.escape || key == KeyCode.back){
|
||||||
|
Core.app.post(this::hide);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//updates sum requirements
|
//updates sum requirements
|
||||||
Runnable update = () -> {
|
Runnable update = () -> {
|
||||||
@@ -79,7 +87,7 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
|||||||
update.run();
|
update.run();
|
||||||
rebuildItems.run();
|
rebuildItems.run();
|
||||||
});
|
});
|
||||||
});
|
}).width(204);
|
||||||
|
|
||||||
buttons.button("@launch.text", Icon.ok, () -> {
|
buttons.button("@launch.text", Icon.ok, () -> {
|
||||||
universe.updateLoadout(core, selected);
|
universe.updateLoadout(core, selected);
|
||||||
|
|||||||
@@ -34,14 +34,6 @@ public class PausedDialog extends BaseDialog{
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(!mobile){
|
if(!mobile){
|
||||||
//TODO localize
|
|
||||||
//TODO capturing is disabled, remove?
|
|
||||||
//cont.label(() -> state.getSector() == null ? "" :
|
|
||||||
//("[lightgray]Next turn in [accent]" + state.getSector().displayTimeRemaining() +
|
|
||||||
// (state.rules.winWave > 0 && !state.getSector().isCaptured() ? "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture" : "")))
|
|
||||||
// .visible(() -> state.getSector() != null).colspan(2);
|
|
||||||
cont.row();
|
|
||||||
|
|
||||||
float dw = 220f;
|
float dw = 220f;
|
||||||
cont.defaults().width(dw).height(55).pad(5f);
|
cont.defaults().width(dw).height(55).pad(5f);
|
||||||
|
|
||||||
@@ -89,7 +81,7 @@ public class PausedDialog extends BaseDialog{
|
|||||||
cont.buttonRow("@launchcore", Icon.up, () -> {
|
cont.buttonRow("@launchcore", Icon.up, () -> {
|
||||||
hide();
|
hide();
|
||||||
ui.planet.showLaunch(state.getSector(), player.team().core());
|
ui.planet.showLaunch(state.getSector(), player.team().core());
|
||||||
}).disabled(b -> player.team().core() == null);
|
}).disabled(b -> player.team().core() == null || net.client());
|
||||||
|
|
||||||
cont.row();
|
cont.row();
|
||||||
|
|
||||||
@@ -101,7 +93,11 @@ public class PausedDialog extends BaseDialog{
|
|||||||
cont.row();
|
cont.row();
|
||||||
}
|
}
|
||||||
|
|
||||||
cont.buttonRow("@hostserver.mobile", Icon.host, ui.host::show).disabled(b -> net.active());
|
if(state.isCampaign() && net.active()){
|
||||||
|
cont.buttonRow("@research", Icon.tree, ui.research::show);
|
||||||
|
}else{
|
||||||
|
cont.buttonRow("@hostserver.mobile", Icon.host, ui.host::show).disabled(b -> net.active());
|
||||||
|
}
|
||||||
|
|
||||||
cont.buttonRow("@quit", Icon.exit, this::showQuitConfirm).update(s -> {
|
cont.buttonRow("@quit", Icon.exit, this::showQuitConfirm).update(s -> {
|
||||||
s.setText(control.saves.getCurrent() != null && control.saves.getCurrent().isAutosave() ? "@save.quit" : "@quit");
|
s.setText(control.saves.getCurrent() != null && control.saves.getCurrent().isAutosave() ? "@save.quit" : "@quit");
|
||||||
|
|||||||
@@ -217,9 +217,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
public void renderProjections(){
|
public void renderProjections(){
|
||||||
if(hovered != null){
|
if(hovered != null){
|
||||||
planets.drawPlane(hovered, () -> {
|
planets.drawPlane(hovered, () -> {
|
||||||
Draw.color(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() : null;
|
TextureRegion icon = hovered.locked() && !canSelect(hovered) ? Icon.lock.getRegion() : hovered.isAttacked() ? Icon.warning.getRegion() : null;
|
||||||
|
|
||||||
if(icon != null){
|
if(icon != null){
|
||||||
Draw.rect(icon, 0, 0);
|
Draw.rect(icon, 0, 0);
|
||||||
@@ -352,69 +352,80 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
|||||||
stable.clear();
|
stable.clear();
|
||||||
stable.background(Styles.black6);
|
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.image().color(Pal.accent).fillX().height(3f).pad(3f).row();
|
||||||
stable.add(sector.save != null ? sector.save.getPlayTime() : "@sectors.unexplored").row();
|
stable.add(sector.save != null ? sector.save.getPlayTime() : "@sectors.unexplored").row();
|
||||||
if(sector.hasWaves() || sector.hasEnemyBase()){
|
|
||||||
|
if(sector.isAttacked() || sector.hasEnemyBase()){
|
||||||
stable.add("[accent]Difficulty: " + (int)(sector.baseCoverage * 10)).row();
|
stable.add("[accent]Difficulty: " + (int)(sector.baseCoverage * 10)).row();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO sector damage is disabled, remove when finalized
|
if(sector.isAttacked()){
|
||||||
/*
|
|
||||||
if(sector.hasBase() && sector.hasWaves()){
|
|
||||||
//TODO localize when finalized
|
//TODO localize when finalized
|
||||||
//these mechanics are likely to change and as such are not added to the bundle
|
//these mechanics are likely to change and as such are not added to the bundle
|
||||||
stable.add("[scarlet]Under attack!");
|
stable.add("[scarlet]Under attack!");
|
||||||
stable.row();
|
stable.row();
|
||||||
stable.add("[accent]" + Mathf.ceil(sectorDestructionTurns - (sector.getSecondsPassed() * 60) / turnDuration) + " turn(s)\nuntil destruction");
|
stable.add("[accent]" + (int)(sector.info.damage * 100) + "% damaged");
|
||||||
stable.row();
|
stable.row();
|
||||||
}*/
|
}
|
||||||
|
|
||||||
if(sector.save != null){
|
if(sector.save != null){
|
||||||
stable.add("@sectors.resources").row();
|
stable.add("@sectors.resources").row();
|
||||||
stable.table(t -> {
|
stable.table(t -> {
|
||||||
|
if(sector.info.resources.any()){
|
||||||
if(sector.save != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.resources.any()){
|
|
||||||
t.left();
|
t.left();
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
int max = 5;
|
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);
|
t.image(c.icon(Cicon.small)).padRight(3);
|
||||||
if(++idx % max == 0) t.row();
|
if(++idx % max == 0) t.row();
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
t.add("@unknown").color(Color.lightGray);
|
t.add("@unknown").color(Color.lightGray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}).fillX().row();
|
}).fillX().row();
|
||||||
}
|
}
|
||||||
|
|
||||||
//production
|
//production
|
||||||
if(sector.hasBase() && sector.save.meta.hasProduction){
|
if(sector.hasBase()){
|
||||||
stable.add("@sectors.production").row();
|
Table t = new Table().left();
|
||||||
stable.table(t -> {
|
|
||||||
t.left();
|
|
||||||
|
|
||||||
sector.save.meta.secinfo.production.each((item, stat) -> {
|
float scl = sector.getProductionScale();
|
||||||
int total = (int)(stat.mean * 60);
|
|
||||||
if(total > 1){
|
sector.info.production.each((item, stat) -> {
|
||||||
t.image(item.icon(Cicon.small)).padRight(3);
|
int total = (int)(stat.mean * 60 * scl);
|
||||||
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray);
|
if(total > 1){
|
||||||
t.row();
|
t.image(item.icon(Cicon.small)).padRight(3);
|
||||||
}
|
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray);
|
||||||
});
|
t.row();
|
||||||
}).row();
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(t.getChildren().any()){
|
||||||
|
stable.add("@sectors.production").row();
|
||||||
|
stable.add(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//stored resources
|
//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.add("@sectors.stored").row();
|
||||||
stable.table(t -> {
|
stable.table(t -> {
|
||||||
t.left();
|
t.left();
|
||||||
|
|
||||||
t.table(res -> {
|
t.table(res -> {
|
||||||
ItemSeq items = sector.calculateItems();
|
ItemSeq items = sector.items();
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(ItemStack stack : items){
|
for(ItemStack stack : items){
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public class ResearchDialog extends BaseDialog{
|
|||||||
for(Planet planet : content.planets()){
|
for(Planet planet : content.planets()){
|
||||||
for(Sector sector : planet.sectors){
|
for(Sector sector : planet.sectors){
|
||||||
if(sector.hasSave()){
|
if(sector.hasSave()){
|
||||||
ItemSeq cached = sector.calculateItems();
|
ItemSeq cached = sector.items();
|
||||||
add(cached);
|
add(cached);
|
||||||
cache.put(sector, cached);
|
cache.put(sector, cached);
|
||||||
}
|
}
|
||||||
@@ -164,13 +164,10 @@ public class ResearchDialog extends BaseDialog{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog show(){
|
public Dialog show(){
|
||||||
Core.app.post(() -> {
|
if(net.client()){
|
||||||
if(net.client()){
|
ui.showInfo("@research.multiplayer");
|
||||||
//TODO make this not display every time
|
return null;
|
||||||
//TODO rework this in the future
|
}
|
||||||
ui.showInfo("@campaign.multiplayer");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return super.show();
|
return super.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,7 +250,6 @@ public class SettingsMenuDialog extends SettingsDialog{
|
|||||||
if(!mobile){
|
if(!mobile){
|
||||||
game.checkPref("buildautopause", false);
|
game.checkPref("buildautopause", false);
|
||||||
}
|
}
|
||||||
game.checkPref("mapcenter", true);
|
|
||||||
|
|
||||||
if(steam){
|
if(steam){
|
||||||
game.sliderPref("playerlimit", 16, 2, 32, i -> {
|
game.sliderPref("playerlimit", 16, 2, 32, i -> {
|
||||||
@@ -292,7 +291,7 @@ public class SettingsMenuDialog extends SettingsDialog{
|
|||||||
}
|
}
|
||||||
return s + "%";
|
return s + "%";
|
||||||
});
|
});
|
||||||
graphics.sliderPref("bridgeopacity", 75, 0, 100, 5, s -> s + "%");
|
graphics.sliderPref("bridgeopacity", 100, 0, 100, 5, s -> s + "%");
|
||||||
|
|
||||||
if(!mobile){
|
if(!mobile){
|
||||||
graphics.checkPref("vsync", true, b -> Core.graphics.setVSync(b));
|
graphics.checkPref("vsync", true, b -> Core.graphics.setVSync(b));
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class HudFragment extends Fragment{
|
|||||||
outer:
|
outer:
|
||||||
for(int i = state.wave - 1; i <= state.wave + max; i++){
|
for(int i = state.wave - 1; i <= state.wave + max; i++){
|
||||||
for(SpawnGroup group : state.rules.spawns){
|
for(SpawnGroup group : state.rules.spawns){
|
||||||
if(group.effect == StatusEffects.boss && group.getUnitsSpawned(i) > 0){
|
if(group.effect == StatusEffects.boss && group.getSpawned(i) > 0){
|
||||||
int diff = (i + 2) - state.wave;
|
int diff = (i + 2) - state.wave;
|
||||||
|
|
||||||
//increments at which to warn about incoming guardian
|
//increments at which to warn about incoming guardian
|
||||||
@@ -71,12 +71,17 @@ public class HudFragment extends Fragment{
|
|||||||
//TODO details and stuff
|
//TODO details and stuff
|
||||||
Events.on(SectorCaptureEvent.class, e ->{
|
Events.on(SectorCaptureEvent.class, e ->{
|
||||||
//TODO localize
|
//TODO localize
|
||||||
showToast("Sector[accent] captured[]!");
|
showToast("Sector [accent]" + (e.sector.isBeingPlayed() ? "" : e.sector.name() + " ") + "[white]captured!");
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO localize
|
//TODO localize
|
||||||
Events.on(SectorLoseEvent.class, e -> {
|
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 -> {
|
Events.on(ResetEvent.class, e -> {
|
||||||
@@ -589,6 +594,7 @@ public class HudFragment extends Fragment{
|
|||||||
StringBuilder ibuild = new StringBuilder();
|
StringBuilder ibuild = new StringBuilder();
|
||||||
|
|
||||||
IntFormat wavef = new IntFormat("wave");
|
IntFormat wavef = new IntFormat("wave");
|
||||||
|
IntFormat wavefc = new IntFormat("wave.cap");
|
||||||
IntFormat enemyf = new IntFormat("wave.enemy");
|
IntFormat enemyf = new IntFormat("wave.enemy");
|
||||||
IntFormat enemiesf = new IntFormat("wave.enemies");
|
IntFormat enemiesf = new IntFormat("wave.enemies");
|
||||||
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
|
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
|
||||||
@@ -714,7 +720,11 @@ public class HudFragment extends Fragment{
|
|||||||
|
|
||||||
table.labelWrap(() -> {
|
table.labelWrap(() -> {
|
||||||
builder.setLength(0);
|
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");
|
builder.append("\n");
|
||||||
|
|
||||||
if(state.enemies > 0){
|
if(state.enemies > 0){
|
||||||
@@ -727,7 +737,7 @@ public class HudFragment extends Fragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(state.rules.waveTimer){
|
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){
|
}else if(state.enemies == 0){
|
||||||
builder.append(Core.bundle.get("waiting"));
|
builder.append(Core.bundle.get("waiting"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,12 +111,10 @@ public class MinimapFragment extends Fragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void toggle(){
|
public void toggle(){
|
||||||
if(Core.settings.getBool("mapcenter")){
|
float size = baseSize * zoom * world.width();
|
||||||
float size = baseSize * zoom * world.width();
|
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
|
||||||
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
|
panx = (size/2f - player.x() / (world.width() * tilesize) * size) / zoom;
|
||||||
panx = (size/2f - player.x() / (world.width() * tilesize) * size) / zoom;
|
pany = (size*ratio/2f - player.y() / (world.height() * tilesize) * size*ratio) / zoom;
|
||||||
pany = (size*ratio/2f - player.y() / (world.height() * tilesize) * size*ratio) / zoom;
|
|
||||||
}
|
|
||||||
shown = !shown;
|
shown = !shown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ import java.util.*;
|
|||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
public class Block extends UnlockableContent{
|
public class Block extends UnlockableContent{
|
||||||
public static final int crackRegions = 8, maxCrackSize = 9;
|
|
||||||
|
|
||||||
public boolean hasItems;
|
public boolean hasItems;
|
||||||
public boolean hasLiquids;
|
public boolean hasLiquids;
|
||||||
public boolean hasPower;
|
public boolean hasPower;
|
||||||
@@ -212,8 +210,6 @@ public class Block extends UnlockableContent{
|
|||||||
public @Load("@-team") TextureRegion teamRegion;
|
public @Load("@-team") TextureRegion teamRegion;
|
||||||
public TextureRegion[] teamRegions;
|
public TextureRegion[] teamRegions;
|
||||||
|
|
||||||
//TODO make this not static
|
|
||||||
public static TextureRegion[][] cracks;
|
|
||||||
protected static final Seq<Tile> tempTiles = new Seq<>();
|
protected static final Seq<Tile> tempTiles = new Seq<>();
|
||||||
protected static final Seq<Building> tempTileEnts = new Seq<>();
|
protected static final Seq<Building> tempTileEnts = new Seq<>();
|
||||||
|
|
||||||
@@ -687,15 +683,6 @@ public class Block extends UnlockableContent{
|
|||||||
public void load(){
|
public void load(){
|
||||||
region = Core.atlas.find(name);
|
region = Core.atlas.find(name);
|
||||||
|
|
||||||
if(cracks == null || (cracks[0][0].texture != null && cracks[0][0].texture.isDisposed())){
|
|
||||||
cracks = new TextureRegion[maxCrackSize][crackRegions];
|
|
||||||
for(int size = 1; size <= maxCrackSize; size++){
|
|
||||||
for(int i = 0; i < crackRegions; i++){
|
|
||||||
cracks[size - 1][i] = Core.atlas.find("cracks-" + size + "-" + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentRegions.loadRegions(this);
|
ContentRegions.loadRegions(this);
|
||||||
|
|
||||||
//load specific team regions
|
//load specific team regions
|
||||||
|
|||||||
@@ -267,6 +267,10 @@ public class Tile implements Position, QuadTreeObject, Displayable{
|
|||||||
Geometry.circle(x, y, world.width(), world.height(), radius, cons);
|
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(){
|
public void recache(){
|
||||||
if(!headless && !world.isGenerating()){
|
if(!headless && !world.isGenerating()){
|
||||||
renderer.blocks.floor.recacheTile(this);
|
renderer.blocks.floor.recacheTile(this);
|
||||||
@@ -332,6 +336,11 @@ public class Tile implements Position, QuadTreeObject, Displayable{
|
|||||||
recache();
|
recache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the overlay without a recache. */
|
||||||
|
public void setOverlayQuiet(Block block){
|
||||||
|
this.overlay = (Floor)block;
|
||||||
|
}
|
||||||
|
|
||||||
public void clearOverlay(){
|
public void clearOverlay(){
|
||||||
setOverlayID((short)0);
|
setOverlayID((short)0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,9 +121,7 @@ public class LaunchPad extends Block{
|
|||||||
|
|
||||||
return Core.bundle.format("launch.destination",
|
return Core.bundle.format("launch.destination",
|
||||||
dest == null ? Core.bundle.get("sectors.nonelaunch") :
|
dest == null ? Core.bundle.get("sectors.nonelaunch") :
|
||||||
dest.preset == null ?
|
"[accent]" + dest.name());
|
||||||
"[accent]Sector " + dest.id :
|
|
||||||
"[accent]" + dest.preset.localizedName);
|
|
||||||
}).pad(4);
|
}).pad(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +211,7 @@ public class LaunchPad extends Block{
|
|||||||
//actually launch the items upon removal
|
//actually launch the items upon removal
|
||||||
if(team() == state.rules.defaultTeam){
|
if(team() == state.rules.defaultTeam){
|
||||||
if(destsec != null && (destsec != state.rules.sector || net.client())){
|
if(destsec != null && (destsec != state.rules.sector || net.client())){
|
||||||
ItemSeq dest = destsec.getExtraItems();
|
ItemSeq dest = new ItemSeq();
|
||||||
|
|
||||||
for(ItemStack stack : stacks){
|
for(ItemStack stack : stacks){
|
||||||
dest.add(stack);
|
dest.add(stack);
|
||||||
@@ -223,7 +221,7 @@ public class LaunchPad extends Block{
|
|||||||
Events.fire(new LaunchItemEvent(stack));
|
Events.fire(new LaunchItemEvent(stack));
|
||||||
}
|
}
|
||||||
|
|
||||||
destsec.setExtraItems(dest);
|
destsec.addItems(dest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import mindustry.annotations.Annotations.*;
|
|||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
|
import mindustry.logic.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.consumers.*;
|
import mindustry.world.consumers.*;
|
||||||
import mindustry.world.meta.*;
|
import mindustry.world.meta.*;
|
||||||
@@ -80,11 +81,16 @@ public class ForceProjector extends Block{
|
|||||||
Draw.color();
|
Draw.color();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ForceBuild extends Building{
|
public class ForceBuild extends Building implements Ranged{
|
||||||
public boolean broken = true;
|
public boolean broken = true;
|
||||||
public float buildup, radscl, hit, warmup, phaseHeat;
|
public float buildup, radscl, hit, warmup, phaseHeat;
|
||||||
public ForceDraw drawer;
|
public ForceDraw drawer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float range(){
|
||||||
|
return realRadius();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void created(){
|
public void created(){
|
||||||
super.created();
|
super.created();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import mindustry.annotations.Annotations.*;
|
|||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
|
import mindustry.logic.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.meta.*;
|
import mindustry.world.meta.*;
|
||||||
|
|
||||||
@@ -55,11 +56,16 @@ public class MendProjector extends Block{
|
|||||||
Drawf.dashCircle(x * tilesize + offset, y * tilesize + offset, range, Pal.accent);
|
Drawf.dashCircle(x * tilesize + offset, y * tilesize + offset, range, Pal.accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MendBuild extends Building{
|
public class MendBuild extends Building implements Ranged{
|
||||||
float heat;
|
float heat;
|
||||||
float charge = Mathf.random(reload);
|
float charge = Mathf.random(reload);
|
||||||
float phaseHeat;
|
float phaseHeat;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float range(){
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateTile(){
|
public void updateTile(){
|
||||||
heat = Mathf.lerpDelta(heat, consValid() || cheating() ? 1f : 0f, 0.08f);
|
heat = Mathf.lerpDelta(heat, consValid() || cheating() ? 1f : 0f, 0.08f);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import arc.util.io.*;
|
|||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
|
import mindustry.logic.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.meta.*;
|
import mindustry.world.meta.*;
|
||||||
|
|
||||||
@@ -60,11 +61,16 @@ public class OverdriveProjector extends Block{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OverdriveBuild extends Building{
|
public class OverdriveBuild extends Building implements Ranged{
|
||||||
float heat;
|
float heat;
|
||||||
float charge = Mathf.random(reload);
|
float charge = Mathf.random(reload);
|
||||||
float phaseHeat;
|
float phaseHeat;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float range(){
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawLight(){
|
public void drawLight(){
|
||||||
Drawf.light(team, x, y, 50f * efficiency(), baseColor, 0.7f * efficiency());
|
Drawf.light(team, x, y, 50f * efficiency(), baseColor, 0.7f * efficiency());
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import mindustry.world.meta.values.*;
|
|||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
public class ItemTurret extends Turret{
|
public class ItemTurret extends Turret{
|
||||||
public int maxAmmo = 30;
|
|
||||||
public ObjectMap<Item, BulletType> ammoTypes = new ObjectMap<>();
|
public ObjectMap<Item, BulletType> ammoTypes = new ObjectMap<>();
|
||||||
|
|
||||||
public ItemTurret(String name){
|
public ItemTurret(String name){
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import mindustry.entities.bullet.*;
|
|||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
|
import mindustry.world.*;
|
||||||
import mindustry.world.consumers.*;
|
import mindustry.world.consumers.*;
|
||||||
import mindustry.world.meta.*;
|
import mindustry.world.meta.*;
|
||||||
import mindustry.world.meta.values.*;
|
import mindustry.world.meta.values.*;
|
||||||
@@ -83,7 +84,9 @@ public class LiquidTurret extends Turret{
|
|||||||
int tr = (int)(range / tilesize);
|
int tr = (int)(range / tilesize);
|
||||||
for(int x = -tr; x <= tr; x++){
|
for(int x = -tr; x <= tr; x++){
|
||||||
for(int y = -tr; y <= tr; y++){
|
for(int y = -tr; y <= tr; y++){
|
||||||
if(Fires.has(x + tile.x, y + tile.y)){
|
Tile other = world.tileWorld(x + tile.x, y + tile.y);
|
||||||
|
//do not extinguish fires on other team blocks
|
||||||
|
if(other != null && Fires.has(x + tile.x, y + tile.y) && (other.build == null || other.team() == team)){
|
||||||
target = Fires.get(x + tile.x, y + tile.y);
|
target = Fires.get(x + tile.x, y + tile.y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package mindustry.world.blocks.defense.turrets;
|
package mindustry.world.blocks.defense.turrets;
|
||||||
|
|
||||||
import mindustry.entities.bullet.*;
|
import mindustry.entities.bullet.*;
|
||||||
|
import mindustry.logic.*;
|
||||||
import mindustry.world.meta.*;
|
import mindustry.world.meta.*;
|
||||||
|
|
||||||
public class PowerTurret extends Turret{
|
public class PowerTurret extends Turret{
|
||||||
@@ -33,6 +34,15 @@ public class PowerTurret extends Turret{
|
|||||||
super.updateTile();
|
super.updateTile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double sense(LAccess sensor){
|
||||||
|
return switch(sensor){
|
||||||
|
case ammo -> power.status;
|
||||||
|
case ammoCapacity -> 1;
|
||||||
|
default -> super.sense(sensor);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BulletType useAmmo(){
|
public BulletType useAmmo(){
|
||||||
//nothing used directly
|
//nothing used directly
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public abstract class Turret extends Block{
|
|||||||
public Effect ammoUseEffect = Fx.none;
|
public Effect ammoUseEffect = Fx.none;
|
||||||
public Sound shootSound = Sounds.shoot;
|
public Sound shootSound = Sounds.shoot;
|
||||||
|
|
||||||
|
public int maxAmmo = 30;
|
||||||
public int ammoPerShot = 1;
|
public int ammoPerShot = 1;
|
||||||
public float ammoEjectBack = 1f;
|
public float ammoEjectBack = 1f;
|
||||||
public float range = 50f;
|
public float range = 50f;
|
||||||
@@ -192,6 +193,8 @@ public abstract class Turret extends Block{
|
|||||||
@Override
|
@Override
|
||||||
public double sense(LAccess sensor){
|
public double sense(LAccess sensor){
|
||||||
return switch(sensor){
|
return switch(sensor){
|
||||||
|
case ammo -> totalAmmo;
|
||||||
|
case ammoCapacity -> maxAmmo;
|
||||||
case rotation -> rotation;
|
case rotation -> rotation;
|
||||||
case shootX -> targetPos.x;
|
case shootX -> targetPos.x;
|
||||||
case shootY -> targetPos.y;
|
case shootY -> targetPos.y;
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class Conveyor extends Block implements Autotiler{
|
|||||||
lastInserted = build.lastInserted;
|
lastInserted = build.lastInserted;
|
||||||
mid = build.mid;
|
mid = build.mid;
|
||||||
minitem = build.minitem;
|
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){
|
if(front() instanceof StackConveyorBuild e && e.team == team){
|
||||||
// sleep if its occupied
|
// sleep if its occupied
|
||||||
if(e.link == -1){
|
if(e.link == -1){
|
||||||
e.items.addAll(items);
|
e.items.add(items);
|
||||||
e.lastItem = lastItem;
|
e.lastItem = lastItem;
|
||||||
e.link = tile.pos();
|
e.link = tile.pos();
|
||||||
// ▲ to | from ▼
|
// ▲ to | from ▼
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ public class SwitchBlock extends Block{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean config(){
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte version(){
|
public byte version(){
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -196,6 +196,21 @@ public class CoreBlock extends StorageBlock{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyed(){
|
||||||
|
super.onDestroyed();
|
||||||
|
|
||||||
|
//add a spawn to the map for future reference - waves should be disabled, so it shouldn't matter
|
||||||
|
if(state.isCampaign() && team == state.rules.waveTeam){
|
||||||
|
//do not recache
|
||||||
|
tile.setOverlayQuiet(Blocks.spawn);
|
||||||
|
|
||||||
|
if(!spawner.getSpawns().contains(tile)){
|
||||||
|
spawner.getSpawns().add(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawLight(){
|
public void drawLight(){
|
||||||
Drawf.light(team, x, y, 30f * size, Pal.accent, 0.5f + Mathf.absin(20f, 0.1f));
|
Drawf.light(team, x, y, 30f * size, Pal.accent, 0.5f + Mathf.absin(20f, 0.1f));
|
||||||
@@ -310,23 +325,6 @@ public class CoreBlock extends StorageBlock{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyed(){
|
|
||||||
super.onDestroyed();
|
|
||||||
|
|
||||||
if(state.isCampaign() && team == state.rules.waveTeam){
|
|
||||||
//do not recache
|
|
||||||
world.setGenerating(true);
|
|
||||||
tile.setOverlay(Blocks.spawn);
|
|
||||||
world.setGenerating(false);
|
|
||||||
|
|
||||||
if(!spawner.getSpawns().contains(tile)){
|
|
||||||
spawner.getSpawns().add(tile);
|
|
||||||
}
|
|
||||||
spawner.doShockwave(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void placed(){
|
public void placed(){
|
||||||
super.placed();
|
super.placed();
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class StorageBlock extends Block{
|
|||||||
public void overwrote(Seq<Building> previous){
|
public void overwrote(Seq<Building> previous){
|
||||||
for(Building other : previous){
|
for(Building other : previous){
|
||||||
if(other.items != null){
|
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){
|
public void add(Item item, int amount){
|
||||||
add(item.id, 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){
|
public void remove(Item item, int amount){
|
||||||
amount = Math.min(amount, items[item.id]);
|
amount = Math.min(amount, items[item.id]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.jvmargs=-Xms256m -Xmx1024m
|
org.gradle.jvmargs=-Xms256m -Xmx1024m
|
||||||
archash=dfcb9ab4b9f9bb977ed3cff4b8a16c22e076368a
|
archash=46ebdb4aeb1e03ca6b3b4c27a93533dc92278a33
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class SectorTests{
|
|||||||
outer:
|
outer:
|
||||||
for(int i = 1; i <= 1000; i++){
|
for(int i = 1; i <= 1000; i++){
|
||||||
for(SpawnGroup spawn : spawns){
|
for(SpawnGroup spawn : spawns){
|
||||||
if(spawn.effect == StatusEffects.boss && spawn.getUnitsSpawned(i) > 0){
|
if(spawn.effect == StatusEffects.boss && spawn.getSpawned(i) > 0){
|
||||||
bossWave = i;
|
bossWave = i;
|
||||||
break outer;
|
break outer;
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ public class SectorTests{
|
|||||||
for(int i = 1; i <= bossWave; i++){
|
for(int i = 1; i <= bossWave; i++){
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for(SpawnGroup spawn : spawns){
|
for(SpawnGroup spawn : spawns){
|
||||||
total += spawn.getUnitsSpawned(i);
|
total += spawn.getSpawned(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNotEquals(0, total, "Sector " + zone + " has no spawned enemies at wave " + i);
|
assertNotEquals(0, total, "Sector " + zone + " has no spawned enemies at wave " + i);
|
||||||
|
|||||||
@@ -63,11 +63,6 @@ public class FakeGraphics extends Graphics{
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getRawDeltaTime(){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getFramesPerSecond(){
|
public int getFramesPerSecond(){
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -153,9 +153,9 @@ public class Generators{
|
|||||||
|
|
||||||
ImagePacker.generate("cracks", () -> {
|
ImagePacker.generate("cracks", () -> {
|
||||||
RidgedPerlin r = new RidgedPerlin(1, 3);
|
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 dim = size * 32;
|
||||||
int steps = Block.crackRegions;
|
int steps = BlockRenderer.crackRegions;
|
||||||
for(int i = 0; i < steps; i++){
|
for(int i = 0; i < steps; i++){
|
||||||
float fract = i / (float)steps;
|
float fract = i / (float)steps;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user