too many things to list
This commit is contained in:
@@ -86,8 +86,6 @@ public class Vars implements Loadable{
|
||||
public static final float logicItemTransferRange = 45f;
|
||||
/** duration of time between turns in ticks */
|
||||
public static final float turnDuration = 2 * Time.toMinutes;
|
||||
/** turns needed to destroy a sector completely */
|
||||
public static final float sectorDestructionTurns = 2f;
|
||||
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||
public static final float minArmorDamage = 0.1f;
|
||||
/** launch animation duration */
|
||||
|
||||
@@ -450,7 +450,7 @@ public class Pathfinder implements Runnable{
|
||||
* Data for a flow field to some set of 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. */
|
||||
protected int refreshRate;
|
||||
/** Team this path is for. Set before using. */
|
||||
@@ -459,7 +459,7 @@ public class Pathfinder implements Runnable{
|
||||
protected PathCost cost = costTypes.get(costGround);
|
||||
|
||||
/** 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 */
|
||||
int[][] searches;
|
||||
/** search frontier, these are Pos objects */
|
||||
|
||||
@@ -23,11 +23,21 @@ public class WaveSpawner{
|
||||
private Seq<Tile> spawns = new Seq<>();
|
||||
private boolean spawning = false;
|
||||
private boolean any = false;
|
||||
private Tile firstSpawn = null;
|
||||
|
||||
public WaveSpawner(){
|
||||
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(){
|
||||
return spawns.size;
|
||||
}
|
||||
@@ -47,7 +57,7 @@ public class WaveSpawner{
|
||||
for(SpawnGroup group : state.rules.spawns){
|
||||
if(group.type == null) continue;
|
||||
|
||||
int spawned = group.getUnitsSpawned(state.wave - 1);
|
||||
int spawned = group.getSpawned(state.wave - 1);
|
||||
|
||||
if(group.type.flying){
|
||||
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));
|
||||
}
|
||||
|
||||
public void eachGroundSpawn(Intc2 cons){
|
||||
eachGroundSpawn((x, y, shock) -> cons.get(world.toTile(x), world.toTile(y)));
|
||||
}
|
||||
|
||||
private void eachGroundSpawn(SpawnConsumer cons){
|
||||
for(Tile spawn : spawns){
|
||||
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
||||
|
||||
@@ -7,6 +7,7 @@ import mindustry.ai.formations.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
public class FormationAI extends AIController implements FormationMember{
|
||||
public Unit leader;
|
||||
@@ -57,6 +58,30 @@ public class FormationAI extends AIController implements FormationMember{
|
||||
}else{
|
||||
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
|
||||
|
||||
@@ -24,7 +24,9 @@ public class MinerAI extends AIController{
|
||||
}
|
||||
|
||||
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
|
||||
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
||||
|
||||
@@ -23,7 +23,7 @@ public class RepairAI extends AIController{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -33,12 +33,14 @@ public class RepairAI extends AIController{
|
||||
|
||||
@Override
|
||||
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 == null){
|
||||
super.updateTargeting();
|
||||
}else{
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1713,7 +1713,7 @@ public class Blocks implements ContentList{
|
||||
despawnEffect = Fx.instBomb;
|
||||
trailSpacing = 20f;
|
||||
damage = 1350;
|
||||
tileDamageMultiplier = 0.5f;
|
||||
tileDamageMultiplier = 0.3f;
|
||||
speed = brange;
|
||||
hitShake = 6f;
|
||||
ammoMultiplier = 1f;
|
||||
|
||||
@@ -510,7 +510,7 @@ public class Bullets implements ContentList{
|
||||
speed = 4f;
|
||||
knockback = 1.3f;
|
||||
puddleSize = 8f;
|
||||
damage = 6f;
|
||||
damage = 5f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 2f;
|
||||
statusDuration = 60f * 4f;
|
||||
|
||||
@@ -5,10 +5,12 @@ import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.type.Weather.*;
|
||||
import mindustry.world.*;
|
||||
@@ -84,20 +86,41 @@ public class Logic implements ApplicationListener{
|
||||
Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack));
|
||||
|
||||
//when loading a 'damaged' sector, propagate the damage
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
Events.on(SaveLoadEvent.class, e -> {
|
||||
if(state.isCampaign()){
|
||||
long seconds = state.rules.sector.getSecondsPassed();
|
||||
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
|
||||
//float turnsPassed = seconds / (turnDuration / 60f);
|
||||
//how much wave time has passed
|
||||
int wavesPassed = state.rules.sector.getWavesPassed();
|
||||
|
||||
//TODO sector damage disabled for now
|
||||
//if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
|
||||
// SectorDamage.apply(turnsPassed / sectorDestructionTurns);
|
||||
//}
|
||||
//reset passed waves
|
||||
state.rules.sector.setWavesPassed(0);
|
||||
|
||||
//wave has passed, remove all enemies, they are assumed to be dead
|
||||
if(wavesPassed > 0){
|
||||
Groups.unit.each(u -> {
|
||||
if(u.team == state.rules.waveTeam){
|
||||
u.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(wavesPassed > 0){
|
||||
//simulate wave counter moving forward
|
||||
state.wave += wavesPassed;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
}
|
||||
|
||||
//reset damage display
|
||||
state.rules.sector.setDamage(0f);
|
||||
|
||||
//simulate damage if applicable
|
||||
if(wavesPassed > 0){
|
||||
SectorDamage.applyCalculatedDamage(wavesPassed);
|
||||
}
|
||||
|
||||
//waves depend on attack status.
|
||||
state.rules.waves = state.rules.sector.isUnderAttack();
|
||||
|
||||
//add resources based on turns passed
|
||||
if(state.rules.sector.save != null && core != null){
|
||||
@@ -121,7 +144,9 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
state.rules.sector.setSecondsPassed(0);
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
//enable infinite ammo for wave team by default
|
||||
state.rules.waveTeam.rules().infiniteAmmo = true;
|
||||
|
||||
@@ -129,6 +154,13 @@ public class Logic implements ApplicationListener{
|
||||
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. */
|
||||
@@ -199,8 +231,6 @@ public class Logic implements ApplicationListener{
|
||||
state.rules.waves = false;
|
||||
}
|
||||
|
||||
//TODO capturing is disabled
|
||||
/*
|
||||
//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()){
|
||||
//the sector has been conquered - waves get disabled
|
||||
@@ -213,7 +243,7 @@ public class Logic implements ApplicationListener{
|
||||
if(!headless){
|
||||
control.saves.saveSector(state.rules.sector);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}else{
|
||||
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
|
||||
state.gameOver = true;
|
||||
@@ -266,6 +296,15 @@ public class Logic implements ApplicationListener{
|
||||
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
|
||||
public void dispose(){
|
||||
//save the settings before quitting
|
||||
|
||||
@@ -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 whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
||||
public final boolean unlockedNow(){
|
||||
return unlocked || alwaysUnlocked || !state.isCampaign();
|
||||
public boolean unlockedNow(){
|
||||
return unlocked() || !state.isCampaign();
|
||||
}
|
||||
|
||||
public final boolean locked(){
|
||||
public boolean locked(){
|
||||
return !unlocked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ public class WaveGraph extends Table{
|
||||
int sum = 0;
|
||||
|
||||
for(SpawnGroup spawn : groups){
|
||||
int spawned = spawn.getUnitsSpawned(i);
|
||||
int spawned = spawn.getSpawned(i);
|
||||
values[index][spawn.type.id] += spawned;
|
||||
if(spawned > 0){
|
||||
used.add(spawn.type);
|
||||
|
||||
@@ -137,6 +137,15 @@ public abstract class BulletType extends Content{
|
||||
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. */
|
||||
public float range(){
|
||||
return Math.max(speed * lifetime * (1f - drag), range);
|
||||
|
||||
@@ -44,6 +44,12 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
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
|
||||
public float range(){
|
||||
return length;
|
||||
|
||||
@@ -88,6 +88,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
case rotation -> rotation;
|
||||
case health -> health;
|
||||
case maxHealth -> maxHealth;
|
||||
case ammo -> state.rules.unitAmmo ? type.ammoCapacity : ammo;
|
||||
case ammoCapacity -> type.ammoCapacity;
|
||||
case x -> x;
|
||||
case y -> y;
|
||||
case team -> team.id;
|
||||
|
||||
@@ -166,7 +166,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
|
||||
Weapon weapon = mount.weapon;
|
||||
|
||||
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));
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ public class DefaultWaves{
|
||||
begin = f;
|
||||
end = f + next >= cap ? never : f + next;
|
||||
max = 14;
|
||||
unitScaling = rand.random(1f, 2f);
|
||||
unitScaling = rand.random(1f, 3f);
|
||||
shields = shieldAmount;
|
||||
shieldScaling = shieldsPerWave;
|
||||
spacing = space;
|
||||
@@ -329,7 +329,7 @@ public class DefaultWaves{
|
||||
|
||||
while(step <= cap){
|
||||
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));
|
||||
|
||||
@@ -82,6 +82,8 @@ public class Rules{
|
||||
public Seq<WeatherEntry> weather = new Seq<>(1);
|
||||
/** Blocks that cannot be placed. */
|
||||
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. */
|
||||
public boolean lighting = false;
|
||||
/** Whether enemy lighting is visible.
|
||||
|
||||
@@ -5,6 +5,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
@@ -38,6 +39,10 @@ public class SectorInfo{
|
||||
public @Nullable Sector destination;
|
||||
/** Resources known to occur at this sector. */
|
||||
public Seq<UnlockableContent> resources = new Seq<>();
|
||||
|
||||
/** Special variables for simulation. */
|
||||
public float sumHealth, sumRps, sumDps, waveHealthBase, waveHealthSlope, waveDpsBase, waveDpsSlope;
|
||||
|
||||
/** Time spent at this sector. Do not use unless you know what you're doing. */
|
||||
public transient float internalTimeSpent;
|
||||
|
||||
@@ -99,6 +104,9 @@ public class SectorInfo{
|
||||
|
||||
//update sector's internal time spent counter
|
||||
state.rules.sector.setTimeSpent(internalTimeSpent);
|
||||
state.rules.sector.setUnderAttack(state.rules.waves);
|
||||
|
||||
SectorDamage.writeParameters(this);
|
||||
}
|
||||
|
||||
/** Update averages of various stats, updates some special sector logic.
|
||||
|
||||
@@ -52,8 +52,8 @@ public class SpawnGroup implements Serializable{
|
||||
//serialization use only
|
||||
}
|
||||
|
||||
/** Returns the amount of units spawned on a specific wave. */
|
||||
public int getUnitsSpawned(int wave){
|
||||
/** @return amount of units spawned on a specific wave. */
|
||||
public int getSpawned(int wave){
|
||||
if(spacing == 0) spacing = 1;
|
||||
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
|
||||
return 0;
|
||||
@@ -61,6 +61,11 @@ public class SpawnGroup implements Serializable{
|
||||
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.
|
||||
* This method does not add() the unit.
|
||||
@@ -76,7 +81,7 @@ public class SpawnGroup implements Serializable{
|
||||
unit.addItem(items.item, items.amount);
|
||||
}
|
||||
|
||||
unit.shield(Math.max(shields + shieldScaling*(wave - begin), 0));
|
||||
unit.shield = getShield(wave);
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ public class Team implements Comparable<Team>{
|
||||
Color.valueOf("ffd37f"), Color.valueOf("eab678"), Color.valueOf("d4816b")),
|
||||
crux = new Team(2, "crux", Color.valueOf("f25555"),
|
||||
Color.valueOf("fc8e6c"), Color.valueOf("f25555"), Color.valueOf("a04553")),
|
||||
green = new Team(3, "green", Color.valueOf("4dd98b")),
|
||||
purple = new Team(4, "purple", Color.valueOf("9a4bdf")),
|
||||
blue = new Team(5, "blue", Color.royal.cpy());
|
||||
green = new Team(3, "green", Color.valueOf("54d67d")),
|
||||
purple = new Team(4, "purple", Color.valueOf("995bb0")),
|
||||
blue = new Team(5, "blue", Color.valueOf("5a4deb"));
|
||||
|
||||
static{
|
||||
Mathf.rand.setSeed(8);
|
||||
|
||||
@@ -6,6 +6,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
|
||||
@@ -55,7 +56,7 @@ 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);
|
||||
return planet.sectors.select(s -> s.isUnderAttack() && s.hasBase() && !s.isBeingPlayed() && s.getWavesPassed() > 0);
|
||||
}
|
||||
|
||||
/** Update planet rotations, global time and relevant state. */
|
||||
@@ -138,11 +139,23 @@ public class Universe{
|
||||
|
||||
//increment seconds passed for this sector by the time that just passed with this turn
|
||||
if(!sector.isBeingPlayed()){
|
||||
sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed);
|
||||
int secPassed = sector.getSecondsPassed() + actuallyPassed;
|
||||
|
||||
sector.setSecondsPassed(secPassed);
|
||||
|
||||
boolean attacked = sector.isUnderAttack();
|
||||
|
||||
int wavesPassed = (int)(secPassed*60f / sector.save.meta.rules.waveSpacing);
|
||||
float damage = attacked ? SectorDamage.getDamage(sector.save.meta.secinfo, sector.save.meta.rules.waveSpacing, sector.save.meta.wave, wavesPassed) : 0f;
|
||||
|
||||
if(attacked){
|
||||
sector.setWavesPassed(wavesPassed);
|
||||
}
|
||||
|
||||
sector.setDamage(damage);
|
||||
|
||||
//TODO sector damage disabled for now
|
||||
//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
|
||||
Events.fire(new SectorLoseEvent(sector));
|
||||
|
||||
@@ -152,7 +165,14 @@ public class Universe{
|
||||
//clear recieved
|
||||
sector.setExtraItems(new ItemSeq());
|
||||
sector.save = null;
|
||||
}*/
|
||||
sector.setDamage(0f);
|
||||
}else if(attacked && wavesPassed > 0 && sector.save.meta.wave + wavesPassed >= sector.save.meta.rules.winWave && !sector.hasEnemyBase()){
|
||||
//autocapture the sector
|
||||
sector.setUnderAttack(false);
|
||||
|
||||
//fire the event
|
||||
Events.fire(new SectorCaptureEvent(state.rules.sector));
|
||||
}
|
||||
}
|
||||
|
||||
//export to another sector
|
||||
|
||||
@@ -450,6 +450,16 @@ public class TypeIO{
|
||||
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){
|
||||
write.s(liquid == null ? -1 : liquid.id);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ public enum LAccess{
|
||||
powerNetCapacity,
|
||||
powerNetIn,
|
||||
powerNetOut,
|
||||
ammo,
|
||||
ammoCapacity,
|
||||
health,
|
||||
maxHealth,
|
||||
heat,
|
||||
|
||||
@@ -191,6 +191,7 @@ public class LAssembler{
|
||||
|
||||
try{
|
||||
double value = Double.parseDouble(symbol);
|
||||
if(Double.isNaN(value) || Double.isInfinite(value)) value = 0;
|
||||
//this creates a hidden const variable with the specified value
|
||||
String key = "___" + value;
|
||||
return putConst(key, value).id;
|
||||
|
||||
@@ -305,7 +305,7 @@ public class LCanvas extends Table{
|
||||
statements.finishLayout();
|
||||
}
|
||||
});
|
||||
}).growX();
|
||||
}).growX().height(38);
|
||||
|
||||
row();
|
||||
|
||||
|
||||
@@ -110,12 +110,12 @@ public class LExecutor{
|
||||
|
||||
public double num(int 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){
|
||||
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){
|
||||
@@ -129,7 +129,7 @@ public class LExecutor{
|
||||
public void setnum(int index, double value){
|
||||
Var v = vars[index];
|
||||
if(v.constant) return;
|
||||
v.numval = value;
|
||||
v.numval = Double.isNaN(value) || Double.isInfinite(value) ? 0 : 0;
|
||||
v.objval = null;
|
||||
v.isobj = false;
|
||||
}
|
||||
@@ -737,7 +737,7 @@ public class LExecutor{
|
||||
v.objval = f.objval;
|
||||
v.isobj = true;
|
||||
}else{
|
||||
v.numval = f.numval;
|
||||
v.numval = Double.isNaN(f.numval) || Double.isInfinite(f.numval) ? 0 : f.numval;
|
||||
v.isobj = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,18 @@ package mindustry.maps;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.abilities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
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 static mindustry.Vars.*;
|
||||
@@ -15,6 +22,264 @@ import static mindustry.Vars.*;
|
||||
public class SectorDamage{
|
||||
//direct damage is for testing only
|
||||
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 waveSpace, int wave, int wavesPassed){
|
||||
float health = info.sumHealth;
|
||||
|
||||
//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(int wavesPassed){
|
||||
//calculate base damage fraction
|
||||
float damage = getDamage(state.secinfo, state.rules.waveSpacing, state.wave, wavesPassed);
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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(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){
|
||||
Tiles tiles = world.tiles;
|
||||
@@ -35,22 +300,62 @@ public class SectorDamage{
|
||||
if(core != null && !frontier.isEmpty()){
|
||||
for(Tile spawner : frontier){
|
||||
//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()));
|
||||
int amount = (int)(path.size * fraction);
|
||||
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);
|
||||
}
|
||||
Seq<Building> removal = new Seq<>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
if(mainFile.exists()){
|
||||
if(mainFile.exists() && Core.settings.getBool("mod-" + meta.name.toLowerCase().replace(" ", "-") + "-enabled", true)){
|
||||
//mobile versions don't support class mods
|
||||
if(ios){
|
||||
throw new IllegalArgumentException("Java class mods are not supported on iOS.");
|
||||
|
||||
@@ -3,7 +3,9 @@ package mindustry.net;
|
||||
import arc.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.io.*;
|
||||
@@ -21,6 +23,18 @@ public class NetworkIO{
|
||||
public static void writeWorld(Player player, OutputStream 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));
|
||||
SaveIO.getSaveWriter().writeStringMap(stream, state.map.tags);
|
||||
|
||||
@@ -44,6 +58,8 @@ public class NetworkIO{
|
||||
state.rules = JsonIO.read(Rules.class, stream.readUTF());
|
||||
state.map = new Map(SaveIO.getSaveWriter().readStringMap(stream));
|
||||
|
||||
Log.info("READ RULES: @", state.rules.researched);
|
||||
|
||||
state.wave = stream.readInt();
|
||||
state.wavetime = stream.readFloat();
|
||||
|
||||
|
||||
@@ -103,9 +103,22 @@ public class Sector{
|
||||
return save != null && !save.meta.rules.waves;
|
||||
}
|
||||
|
||||
/** @return whether waves are present - if true, any bases here will be attacked. */
|
||||
public boolean hasWaves(){
|
||||
return save != null && save.meta.rules.waves;
|
||||
/** @return whether waves are present - if true, any bases here will be attacked.
|
||||
* only applicable to sectors with active player bases. */
|
||||
public boolean isUnderAttack(){
|
||||
return hasBase() && Core.settings.getBool(key("under-attack"), true);
|
||||
}
|
||||
|
||||
public void setUnderAttack(boolean underAttack){
|
||||
Core.settings.put(key("under-attack"), underAttack);
|
||||
}
|
||||
|
||||
public void setWavesPassed(int waves){
|
||||
put("waves-passed", waves);
|
||||
}
|
||||
|
||||
public int getWavesPassed(){
|
||||
return Core.settings.getInt(key("waves-passed"), 0);
|
||||
}
|
||||
|
||||
public boolean hasSave(){
|
||||
@@ -238,6 +251,15 @@ public class Sector{
|
||||
return Core.settings.getInt(key("spawn-position"), Point2.pack(world.width() / 2, world.height() / 2));
|
||||
}
|
||||
|
||||
/** @return sector damage from enemy, 0 to 1 */
|
||||
public float getDamage(){
|
||||
return Core.settings.getFloat(key("damage"), 0f);
|
||||
}
|
||||
|
||||
public void setDamage(float damage){
|
||||
put("damage", damage);
|
||||
}
|
||||
|
||||
/** @return time spent in this sector this turn in ticks. */
|
||||
public float getTimeSpent(){
|
||||
//return currently counting time spent if being played on
|
||||
|
||||
@@ -79,6 +79,8 @@ public class UnitType extends UnlockableContent{
|
||||
public int mineTier = -1;
|
||||
public float buildSpeed = 1f, mineSpeed = 1f;
|
||||
|
||||
/** This is a VERY ROUGH estimate of unit DPS. */
|
||||
public float dpsEstimate = -1;
|
||||
public float clipSize = -1;
|
||||
public boolean canDrown = true;
|
||||
public float engineOffset = 5f, engineSize = 2.5f;
|
||||
@@ -266,6 +268,17 @@ public class UnitType extends UnlockableContent{
|
||||
|
||||
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
|
||||
@@ -436,7 +449,7 @@ public class UnitType extends UnlockableContent{
|
||||
applyColor(unit);
|
||||
|
||||
//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;
|
||||
|
||||
Draw.mixcol(Pal.accent, Mathf.absin(Time.time(), 5f, 0.5f));
|
||||
|
||||
@@ -76,13 +76,15 @@ public class HostDialog extends BaseDialog{
|
||||
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){
|
||||
ui.showException("@server.error", e);
|
||||
}
|
||||
|
||||
@@ -367,8 +367,10 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
local.row();
|
||||
|
||||
TextButton button = local.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version))
|
||||
.width(w).pad(5f).get();
|
||||
TextButton button = local.button("", Styles.cleart, () -> {
|
||||
Events.fire(new ClientPreConnectEvent(host));
|
||||
safeConnect(host.address, host.port, host.version);
|
||||
}).width(w).pad(5f).get();
|
||||
button.clearChildren();
|
||||
buildServer(host, button);
|
||||
}
|
||||
@@ -379,8 +381,10 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
global.row();
|
||||
|
||||
TextButton button = global.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version))
|
||||
.width(w).pad(5f).get();
|
||||
TextButton button = global.button("", Styles.cleart, () -> {
|
||||
Events.fire(new ClientPreConnectEvent(host));
|
||||
safeConnect(host.address, host.port, host.version);
|
||||
}).width(w).pad(5f).get();
|
||||
button.clearChildren();
|
||||
buildServer(host, button);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.input.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
@@ -33,7 +34,14 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
||||
cont.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
|
||||
Runnable update = () -> {
|
||||
@@ -79,7 +87,7 @@ public class LaunchLoadoutDialog extends BaseDialog{
|
||||
update.run();
|
||||
rebuildItems.run();
|
||||
});
|
||||
});
|
||||
}).width(204);
|
||||
|
||||
buttons.button("@launch.text", Icon.ok, () -> {
|
||||
universe.updateLoadout(core, selected);
|
||||
|
||||
@@ -34,12 +34,11 @@ public class PausedDialog extends BaseDialog{
|
||||
});
|
||||
|
||||
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);
|
||||
//TODO localize + move to other wave menu
|
||||
cont.label(() -> state.getSector() == null || state.rules.winWave <= 0 || state.getSector().isCaptured() ? "" :
|
||||
(state.rules.winWave > 0 && !state.getSector().isCaptured() ?
|
||||
(state.wave >= state.rules.winWave ? "\n[lightgray]Defeat remaining enemies to capture" : "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture") : ""))
|
||||
.visible(() -> state.getSector() != null).colspan(2);
|
||||
cont.row();
|
||||
|
||||
float dw = 220f;
|
||||
@@ -89,7 +88,7 @@ public class PausedDialog extends BaseDialog{
|
||||
cont.buttonRow("@launchcore", Icon.up, () -> {
|
||||
hide();
|
||||
ui.planet.showLaunch(state.getSector(), player.team().core());
|
||||
}).disabled(b -> player.team().core() == null);
|
||||
}).disabled(b -> player.team().core() == null || net.client());
|
||||
|
||||
cont.row();
|
||||
|
||||
@@ -101,7 +100,11 @@ public class PausedDialog extends BaseDialog{
|
||||
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 -> {
|
||||
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(){
|
||||
if(hovered != null){
|
||||
planets.drawPlane(hovered, () -> {
|
||||
Draw.color(Color.white, Pal.accent, Mathf.absin(5f, 1f));
|
||||
Draw.color(hovered.isUnderAttack() ? 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.isUnderAttack() ? Icon.warning.getRegion() : null;
|
||||
|
||||
if(icon != null){
|
||||
Draw.rect(icon, 0, 0);
|
||||
@@ -355,20 +355,18 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
stable.add("[accent]" + (sector.preset == null ? sector.id : sector.preset.localizedName)).row();
|
||||
stable.image().color(Pal.accent).fillX().height(3f).pad(3f).row();
|
||||
stable.add(sector.save != null ? sector.save.getPlayTime() : "@sectors.unexplored").row();
|
||||
if(sector.hasWaves() || sector.hasEnemyBase()){
|
||||
if(sector.isUnderAttack() || sector.hasEnemyBase()){
|
||||
stable.add("[accent]Difficulty: " + (int)(sector.baseCoverage * 10)).row();
|
||||
}
|
||||
|
||||
//TODO sector damage is disabled, remove when finalized
|
||||
/*
|
||||
if(sector.hasBase() && sector.hasWaves()){
|
||||
if(sector.isUnderAttack()){
|
||||
//TODO localize when finalized
|
||||
//these mechanics are likely to change and as such are not added to the bundle
|
||||
stable.add("[scarlet]Under attack!");
|
||||
stable.row();
|
||||
stable.add("[accent]" + Mathf.ceil(sectorDestructionTurns - (sector.getSecondsPassed() * 60) / turnDuration) + " turn(s)\nuntil destruction");
|
||||
stable.add("[accent]" + (int)(sector.getDamage() * 100) + "% damaged");
|
||||
stable.row();
|
||||
}*/
|
||||
}
|
||||
|
||||
if(sector.save != null){
|
||||
stable.add("@sectors.resources").row();
|
||||
|
||||
@@ -164,13 +164,10 @@ public class ResearchDialog extends BaseDialog{
|
||||
|
||||
@Override
|
||||
public Dialog show(){
|
||||
Core.app.post(() -> {
|
||||
if(net.client()){
|
||||
//TODO make this not display every time
|
||||
//TODO rework this in the future
|
||||
ui.showInfo("@campaign.multiplayer");
|
||||
}
|
||||
});
|
||||
if(net.client()){
|
||||
ui.showInfo("@research.multiplayer");
|
||||
return null;
|
||||
}
|
||||
|
||||
return super.show();
|
||||
}
|
||||
|
||||
@@ -250,7 +250,6 @@ public class SettingsMenuDialog extends SettingsDialog{
|
||||
if(!mobile){
|
||||
game.checkPref("buildautopause", false);
|
||||
}
|
||||
game.checkPref("mapcenter", true);
|
||||
|
||||
if(steam){
|
||||
game.sliderPref("playerlimit", 16, 2, 32, i -> {
|
||||
|
||||
@@ -54,7 +54,7 @@ public class HudFragment extends Fragment{
|
||||
outer:
|
||||
for(int i = state.wave - 1; i <= state.wave + max; i++){
|
||||
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;
|
||||
|
||||
//increments at which to warn about incoming guardian
|
||||
|
||||
@@ -111,12 +111,10 @@ public class MinimapFragment extends Fragment{
|
||||
}
|
||||
|
||||
public void toggle(){
|
||||
if(Core.settings.getBool("mapcenter")){
|
||||
float size = baseSize * zoom * world.width();
|
||||
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
|
||||
panx = (size/2f - player.x() / (world.width() * tilesize) * size) / zoom;
|
||||
pany = (size*ratio/2f - player.y() / (world.height() * tilesize) * size*ratio) / zoom;
|
||||
}
|
||||
float size = baseSize * zoom * world.width();
|
||||
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
|
||||
panx = (size/2f - player.x() / (world.width() * tilesize) * size) / zoom;
|
||||
pany = (size*ratio/2f - player.y() / (world.height() * tilesize) * size*ratio) / zoom;
|
||||
shown = !shown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.meta.*;
|
||||
@@ -80,11 +81,16 @@ public class ForceProjector extends Block{
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
public class ForceBuild extends Building{
|
||||
public class ForceBuild extends Building implements Ranged{
|
||||
public boolean broken = true;
|
||||
public float buildup, radscl, hit, warmup, phaseHeat;
|
||||
public ForceDraw drawer;
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return realRadius();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(){
|
||||
super.created();
|
||||
|
||||
@@ -9,6 +9,7 @@ import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
@@ -55,11 +56,16 @@ public class MendProjector extends Block{
|
||||
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 charge = Mathf.random(reload);
|
||||
float phaseHeat;
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
heat = Mathf.lerpDelta(heat, consValid() || cheating() ? 1f : 0f, 0.08f);
|
||||
|
||||
@@ -8,6 +8,7 @@ import arc.util.io.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.*;
|
||||
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 charge = Mathf.random(reload);
|
||||
float phaseHeat;
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLight(){
|
||||
Drawf.light(team, x, y, 50f * efficiency(), baseColor, 0.7f * efficiency());
|
||||
|
||||
@@ -19,7 +19,6 @@ import mindustry.world.meta.values.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class ItemTurret extends Turret{
|
||||
public int maxAmmo = 30;
|
||||
public ObjectMap<Item, BulletType> ammoTypes = new ObjectMap<>();
|
||||
|
||||
public ItemTurret(String name){
|
||||
|
||||
@@ -8,6 +8,7 @@ import mindustry.entities.bullet.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.meta.*;
|
||||
import mindustry.world.meta.values.*;
|
||||
@@ -83,7 +84,9 @@ public class LiquidTurret extends Turret{
|
||||
int tr = (int)(range / tilesize);
|
||||
for(int x = -tr; x <= tr; x++){
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.world.blocks.defense.turrets;
|
||||
|
||||
import mindustry.entities.bullet.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class PowerTurret extends Turret{
|
||||
@@ -33,6 +34,15 @@ public class PowerTurret extends Turret{
|
||||
super.updateTile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
return switch(sensor){
|
||||
case ammo -> power.status;
|
||||
case ammoCapacity -> 1;
|
||||
default -> super.sense(sensor);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BulletType useAmmo(){
|
||||
//nothing used directly
|
||||
|
||||
@@ -41,6 +41,7 @@ public abstract class Turret extends Block{
|
||||
public Effect ammoUseEffect = Fx.none;
|
||||
public Sound shootSound = Sounds.shoot;
|
||||
|
||||
public int maxAmmo = 30;
|
||||
public int ammoPerShot = 1;
|
||||
public float ammoEjectBack = 1f;
|
||||
public float range = 50f;
|
||||
@@ -192,6 +193,8 @@ public abstract class Turret extends Block{
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
return switch(sensor){
|
||||
case ammo -> totalAmmo;
|
||||
case ammoCapacity -> maxAmmo;
|
||||
case rotation -> rotation;
|
||||
case shootX -> targetPos.x;
|
||||
case shootY -> targetPos.y;
|
||||
|
||||
@@ -37,6 +37,11 @@ public class SwitchBlock extends Block{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean config(){
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 1;
|
||||
|
||||
Reference in New Issue
Block a user