diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 64b7650e62..85459446aa 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -7,8 +7,6 @@ assignees: ''
---
-**Note**: Do not report any new bugs directly relating to the v6 campaign. They will not be fixed or considered at this time.
-
**Platform**: *Android/iOS/Mac/Windows/Linux*
**Build**: *The build number under the title in the main menu. Required. "LATEST" IS NOT A VERSION, I NEED THE EXACT BUILD NUMBER OF YOUR GAME.*
diff --git a/README.md b/README.md
index 5c9c797297..13f620832f 100644
--- a/README.md
+++ b/README.md
@@ -9,34 +9,34 @@ _[Trello Board](https://trello.com/b/aE2tcUwF/mindustry-40-plans)_
_[Wiki](https://mindustrygame.github.io/wiki)_
_[Javadoc](https://mindustrygame.github.io/docs/)_
-### Contributing
+## Contributing
See [CONTRIBUTING](CONTRIBUTING.md).
-### Building
+## Building
Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases).
If you'd rather compile on your own, follow these instructions.
First, make sure you have [JDK 14](https://adoptopenjdk.net/) installed. Open a terminal in the root directory, `cd` to the Mindustry folder and run the following commands:
-#### Windows
+### Windows
_Running:_ `gradlew desktop:run`
_Building:_ `gradlew desktop:dist`
_Sprite Packing:_ `gradlew tools:pack`
-#### Linux/Mac OS
+### Linux/Mac OS
_Running:_ `./gradlew desktop:run`
_Building:_ `./gradlew desktop:dist`
_Sprite Packing:_ `./gradlew tools:pack`
-#### Server
+### Server
Server builds are bundled with each released build (in Releases). If you'd rather compile on your own, replace 'desktop' with 'server', e.g. `gradlew server:dist`.
-#### Android
+### Android
1. Install the Android SDK [here.](https://developer.android.com/studio#downloads) Make sure you're downloading the "Command line tools only", as Android Studio is not required.
2. Set the `ANDROID_HOME` environment variable to point to your unzipped Android SDK directory.
@@ -44,7 +44,9 @@ Server builds are bundled with each released build (in Releases). If you'd rathe
To debug the application on a connected phone, run `gradlew android:installDebug android:run`.
-##### Troubleshooting
+### Troubleshooting
+
+#### Permission Denied
If the terminal returns `Permission denied` or `Command not found` on Mac/Linux, run `chmod +x ./gradlew` before running `./gradlew`. *This is a one-time procedure.*
@@ -53,11 +55,11 @@ If the terminal returns `Permission denied` or `Command not found` on Mac/Linux,
Gradle may take up to several minutes to download files. Be patient.
After building, the output .JAR file should be in `/desktop/build/libs/Mindustry.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds.
-### Feature Requests
+## Feature Requests
Post feature requests and feedback [here](https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose).
-### Downloads
+## Downloads
[
u.team() == data.team && u.type() == block.unitType)){
+ if(!state.isEditor() && !Groups.unit.contains(u -> u.team() == data.team && u.type == block.unitType)){
Unit unit = block.unitType.create(data.team);
unit.set(data.core());
unit.add();
@@ -68,7 +69,7 @@ public class BaseAI{
if(pos == null) return;
Tmp.v1.rnd(Mathf.random(range));
- int wx = (int)(world.toTile(pos.getX()) + Tmp.v1.x), wy = (int)(world.toTile(pos.getY()) + Tmp.v1.y);
+ int wx = (int)(World.toTile(pos.getX()) + Tmp.v1.x), wy = (int)(World.toTile(pos.getY()) + Tmp.v1.y);
Tile tile = world.tiles.getc(wx, wy);
Seq parts = null;
diff --git a/core/src/mindustry/ai/BlockIndexer.java b/core/src/mindustry/ai/BlockIndexer.java
index 6092c83910..a75b0758ea 100644
--- a/core/src/mindustry/ai/BlockIndexer.java
+++ b/core/src/mindustry/ai/BlockIndexer.java
@@ -8,6 +8,7 @@ import arc.struct.EnumSet;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
@@ -179,8 +180,8 @@ public class BlockIndexer{
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf pred, Cons cons){
intSet.clear();
- int tx = world.toTile(wx);
- int ty = world.toTile(wy);
+ int tx = World.toTile(wx);
+ int ty = World.toTile(wy);
int tileRange = (int)(range / tilesize + 1);
boolean any = false;
diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java
index c24cecd49c..78a596bb9d 100644
--- a/core/src/mindustry/ai/Pathfinder.java
+++ b/core/src/mindustry/ai/Pathfinder.java
@@ -7,6 +7,7 @@ import arc.struct.*;
import arc.util.*;
import arc.util.async.*;
import mindustry.annotations.Annotations.*;
+import mindustry.core.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -85,9 +86,6 @@ public class Pathfinder implements Runnable{
tiles[tile.x][tile.y] = packTile(tile);
}
- //special preset which may help speed things up; this is optional
- preloadPath(getField(state.rules.waveTeam, costGround, fieldCore));
-
start();
});
@@ -105,7 +103,7 @@ public class Pathfinder implements Runnable{
boolean nearLiquid = false, nearSolid = false, nearGround = false;
for(int i = 0; i < 4; i++){
- Tile other = tile.getNearby(i);
+ Tile other = tile.nearby(i);
if(other != null){
if(other.floor().isLiquid) nearLiquid = true;
if(other.solid()) nearSolid = true;
@@ -114,7 +112,7 @@ public class Pathfinder implements Runnable{
}
return PathTile.get(
- tile.build == null ? 0 : Math.min((int)(tile.build.health / 40), 80),
+ tile.build == null || !tile.solid() ? 0 : Math.min((int)(tile.build.health / 40), 80),
tile.getTeamID(),
tile.solid(),
tile.floor().isLiquid,
@@ -444,7 +442,7 @@ public class Pathfinder implements Runnable{
@Override
public void getPositions(IntSeq out){
- out.add(Point2.pack(world.toTile(position.getX()), world.toTile(position.getY())));
+ out.add(Point2.pack(World.toTile(position.getX()), World.toTile(position.getY())));
}
}
@@ -453,7 +451,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. */
@@ -462,7 +460,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 */
diff --git a/core/src/mindustry/ai/WaveSpawner.java b/core/src/mindustry/ai/WaveSpawner.java
index 9cecf32ac1..429ba00587 100644
--- a/core/src/mindustry/ai/WaveSpawner.java
+++ b/core/src/mindustry/ai/WaveSpawner.java
@@ -8,6 +8,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -23,11 +24,21 @@ public class WaveSpawner{
private Seq 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;
}
@@ -38,7 +49,7 @@ public class WaveSpawner{
/** @return true if the player is near a ground spawn point. */
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.hasSpawns() && !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(){
@@ -47,7 +58,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,9 +100,15 @@ 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);
+ if(state.hasSpawns()){
+ for(Tile spawn : spawns){
+ cons.accept(spawn.worldx(), spawn.worldy(), true);
+ }
}
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
@@ -104,7 +121,7 @@ public class WaveSpawner{
//keep moving forward until the max step amount is reached
while(steps++ < maxSteps){
- int tx = world.toTile(core.x + Tmp.v1.x), ty = world.toTile(core.y + Tmp.v1.y);
+ int tx = World.toTile(core.x + Tmp.v1.x), ty = World.toTile(core.y + Tmp.v1.y);
any = false;
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
if(world.solid(x, y)){
@@ -161,7 +178,7 @@ public class WaveSpawner{
}
private void spawnEffect(Unit unit){
- Call.spawnEffect(unit.x, unit.y, unit.type());
+ Call.spawnEffect(unit.x, unit.y, unit.type);
Time.run(30f, unit::add);
}
diff --git a/core/src/mindustry/ai/types/BuilderAI.java b/core/src/mindustry/ai/types/BuilderAI.java
index 8e8c74f986..c3558ca58a 100644
--- a/core/src/mindustry/ai/types/BuilderAI.java
+++ b/core/src/mindustry/ai/types/BuilderAI.java
@@ -79,7 +79,7 @@ public class BuilderAI extends AIController{
float dist = Math.min(cons.dst(unit) - buildingRange, 0);
//make sure you can reach the request in time
- if(dist / unit.type().speed < cons.buildCost * 0.9f){
+ if(dist / unit.type.speed < cons.buildCost * 0.9f){
following = b;
found = true;
}
@@ -112,7 +112,7 @@ public class BuilderAI extends AIController{
@Override
public AIController fallback(){
- return unit.type().flying ? new FlyingAI() : new GroundAI();
+ return unit.type.flying ? new FlyingAI() : new GroundAI();
}
@Override
diff --git a/core/src/mindustry/ai/types/FlyingAI.java b/core/src/mindustry/ai/types/FlyingAI.java
index 0758f9016a..9d4c2d38f9 100644
--- a/core/src/mindustry/ai/types/FlyingAI.java
+++ b/core/src/mindustry/ai/types/FlyingAI.java
@@ -12,7 +12,7 @@ public class FlyingAI extends AIController{
@Override
public void updateMovement(){
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
- if(unit.type().weapons.first().rotate){
+ if(unit.type.weapons.first().rotate){
moveTo(target, unit.range() * 0.8f);
unit.lookAt(target);
}else{
@@ -57,7 +57,7 @@ public class FlyingAI extends AIController{
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
}
- vec.setLength(unit.type().speed);
+ vec.setLength(unit.type.speed);
unit.moveAt(vec);
}
diff --git a/core/src/mindustry/ai/types/FormationAI.java b/core/src/mindustry/ai/types/FormationAI.java
index cbe12a565e..ed175524d8 100644
--- a/core/src/mindustry/ai/types/FormationAI.java
+++ b/core/src/mindustry/ai/types/FormationAI.java
@@ -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;
@@ -26,14 +27,14 @@ public class FormationAI extends AIController implements FormationMember{
@Override
public void updateUnit(){
- UnitType type = unit.type();
+ UnitType type = unit.type;
if(leader.dead){
unit.resetController();
return;
}
- if(unit.type().canBoost && unit.canPassOn()){
+ if(unit.type.canBoost && unit.canPassOn()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
}
@@ -42,7 +43,7 @@ public class FormationAI extends AIController implements FormationMember{
unit.aim(leader.aimX(), leader.aimY());
- if(unit.type().rotateShooting){
+ if(unit.type.rotateShooting){
unit.lookAt(leader.aimX(), leader.aimY());
}else if(unit.moving()){
unit.lookAt(unit.vel.angle());
@@ -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
diff --git a/core/src/mindustry/ai/types/GroundAI.java b/core/src/mindustry/ai/types/GroundAI.java
index 157ed2eb9d..0134a424fc 100644
--- a/core/src/mindustry/ai/types/GroundAI.java
+++ b/core/src/mindustry/ai/types/GroundAI.java
@@ -45,13 +45,13 @@ public class GroundAI extends AIController{
}
}
- if(unit.type().canBoost && !unit.onSolid()){
+ if(unit.type.canBoost && !unit.onSolid()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
}
- if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type().rotateShooting){
- if(unit.type().hasWeapons()){
- unit.lookAt(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
+ if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type.rotateShooting){
+ if(unit.type.hasWeapons()){
+ unit.lookAt(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
diff --git a/core/src/mindustry/ai/types/LogicAI.java b/core/src/mindustry/ai/types/LogicAI.java
index 76b81794a2..1166c2ff96 100644
--- a/core/src/mindustry/ai/types/LogicAI.java
+++ b/core/src/mindustry/ai/types/LogicAI.java
@@ -98,7 +98,7 @@ public class LogicAI extends AIController{
}
}
- if(unit.type().canBoost && !unit.type().flying){
+ if(unit.type.canBoost && !unit.type.flying){
unit.elevation = Mathf.approachDelta(unit.elevation, Mathf.num(boost || unit.onSolid()), 0.08f);
}
@@ -129,7 +129,7 @@ public class LogicAI extends AIController{
@Override
protected boolean shouldShoot(){
- return shoot && !(unit.type().canBoost && boost);
+ return shoot && !(unit.type.canBoost && boost);
}
//always aim for the main target
diff --git a/core/src/mindustry/ai/types/MinerAI.java b/core/src/mindustry/ai/types/MinerAI.java
index a03481768f..aa7d1403c5 100644
--- a/core/src/mindustry/ai/types/MinerAI.java
+++ b/core/src/mindustry/ai/types/MinerAI.java
@@ -19,12 +19,14 @@ public class MinerAI extends AIController{
if(!(unit instanceof Minerc miner) || core == null) return;
- if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type().range)){
+ if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type.range)){
miner.mineTile(null);
}
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){
@@ -34,7 +36,7 @@ public class MinerAI extends AIController{
}
//if inventory is full, drop it off.
- if(unit.stack.amount >= unit.type().itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
+ if(unit.stack.amount >= unit.type.itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
mining = false;
}else{
if(retarget() && targetItem != null){
@@ -42,9 +44,9 @@ public class MinerAI extends AIController{
}
if(ore != null){
- moveTo(ore, unit.type().range / 2f);
+ moveTo(ore, unit.type.range / 2f);
- if(unit.within(ore, unit.type().range)){
+ if(unit.within(ore, unit.type.range)){
miner.mineTile(ore);
}
@@ -61,7 +63,7 @@ public class MinerAI extends AIController{
return;
}
- if(unit.within(core, unit.type().range)){
+ if(unit.within(core, unit.type.range)){
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
}
@@ -70,7 +72,7 @@ public class MinerAI extends AIController{
mining = true;
}
- circle(core, unit.type().range / 1.8f);
+ circle(core, unit.type.range / 1.8f);
}
}
diff --git a/core/src/mindustry/ai/types/RepairAI.java b/core/src/mindustry/ai/types/RepairAI.java
index 5f021ae828..36504f908d 100644
--- a/core/src/mindustry/ai/types/RepairAI.java
+++ b/core/src/mindustry/ai/types/RepairAI.java
@@ -12,7 +12,7 @@ public class RepairAI extends AIController{
if(target instanceof Building){
boolean shoot = false;
- if(target.within(unit, unit.type().range)){
+ if(target.within(unit, unit.type.range)){
unit.aim(target);
shoot = true;
}
@@ -23,8 +23,8 @@ public class RepairAI extends AIController{
}
if(target != null){
- if(!target.within(unit, unit.type().range * 0.65f)){
- moveTo(target, unit.type().range * 0.65f);
+ if(!target.within(unit, unit.type.range * 0.65f) && target instanceof Building){
+ moveTo(target, unit.type.range * 0.65f);
}
unit.lookAt(target);
@@ -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;
}
}
diff --git a/core/src/mindustry/ai/types/SuicideAI.java b/core/src/mindustry/ai/types/SuicideAI.java
index 2eae6e409f..0ef3ece228 100644
--- a/core/src/mindustry/ai/types/SuicideAI.java
+++ b/core/src/mindustry/ai/types/SuicideAI.java
@@ -21,7 +21,7 @@ public class SuicideAI extends GroundAI{
}
if(retarget()){
- target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
+ target = target(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
}
Building core = unit.closestEnemyCore();
@@ -30,11 +30,11 @@ public class SuicideAI extends GroundAI{
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.hasWeapons()){
rotate = true;
- shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
+ shoot = unit.within(target, unit.type.weapons.first().bullet.range() +
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
- if(unit.type().hasWeapons()){
- unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
+ if(unit.type.hasWeapons()){
+ unit.aimLook(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
}
//do not move toward walls or transport blocks
@@ -65,7 +65,7 @@ public class SuicideAI extends GroundAI{
if(!blocked){
moveToTarget = true;
//move towards target directly
- unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
+ unit.moveAt(vec.set(target).sub(unit).limit(unit.type.speed));
}
}
diff --git a/core/src/mindustry/async/PhysicsProcess.java b/core/src/mindustry/async/PhysicsProcess.java
index 07d44d4717..a60dab7f68 100644
--- a/core/src/mindustry/async/PhysicsProcess.java
+++ b/core/src/mindustry/async/PhysicsProcess.java
@@ -57,7 +57,7 @@ public class PhysicsProcess implements AsyncProcess{
PhysicRef ref = entity.physref();
ref.body.layer =
- entity.type().allowLegStep ? layerLegs :
+ entity.type.allowLegStep ? layerLegs :
entity.isGrounded() ? layerGround : layerFlying;
ref.x = entity.x();
ref.y = entity.y();
diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java
index 05ed572f7e..64ebd5c21d 100644
--- a/core/src/mindustry/content/Blocks.java
+++ b/core/src/mindustry/content/Blocks.java
@@ -919,8 +919,8 @@ public class Blocks implements ContentList{
shockMine = new ShockMine("shock-mine"){{
requirements(Category.effect, with(Items.lead, 25, Items.silicon, 12));
hasShadow = false;
- health = 40;
- damage = 23;
+ health = 50;
+ damage = 25;
tileDamage = 7f;
length = 10;
tendrils = 4;
@@ -1353,7 +1353,7 @@ public class Blocks implements ContentList{
size = 5;
unitCapModifier = 20;
- researchCostMultiplier = 0.06f;
+ researchCostMultiplier = 0.05f;
}};
vault = new StorageBlock("vault"){{
@@ -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;
@@ -1912,7 +1912,7 @@ public class Blocks implements ContentList{
new UnitType[]{UnitTypes.antumbra, UnitTypes.eclipse},
new UnitType[]{UnitTypes.arkyid, UnitTypes.toxopid},
new UnitType[]{UnitTypes.scepter, UnitTypes.reign},
- new UnitType[] {UnitTypes.sei, UnitTypes.omura},
+ new UnitType[]{UnitTypes.sei, UnitTypes.omura},
new UnitType[]{UnitTypes.quad, UnitTypes.oct},
new UnitType[]{UnitTypes.vela, UnitTypes.corvus}
);
diff --git a/core/src/mindustry/content/Bullets.java b/core/src/mindustry/content/Bullets.java
index 221dc44e8d..e2d42c0ef0 100644
--- a/core/src/mindustry/content/Bullets.java
+++ b/core/src/mindustry/content/Bullets.java
@@ -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;
diff --git a/core/src/mindustry/content/Fx.java b/core/src/mindustry/content/Fx.java
index 6ca6ccb24d..ca5bf9a0d0 100644
--- a/core/src/mindustry/content/Fx.java
+++ b/core/src/mindustry/content/Fx.java
@@ -56,7 +56,7 @@ public class Fx{
mixcol(Pal.accent, 1f);
alpha(e.fout());
- rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type().icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
+ rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type.icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
alpha(1f);
Lines.stroke(e.fslope() * 1f);
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
@@ -66,7 +66,7 @@ public class Fx{
}),
unitDespawn = new Effect(100f, e -> {
- if(!(e.data instanceof Unit) || e.data().type() == null) return;
+ if(!(e.data instanceof Unit) || e.data().type == null) return;
Unit select = e.data();
float scl = e.fout(Interp.pow2Out);
@@ -74,7 +74,7 @@ public class Fx{
Draw.scl *= scl;
mixcol(Pal.accent, 1f);
- rect(select.type().icon(Cicon.full), select.x, select.y, select.rotation - 90f);
+ rect(select.type.icon(Cicon.full), select.x, select.y, select.rotation - 90f);
reset();
Draw.scl = p;
diff --git a/core/src/mindustry/content/SectorPresets.java b/core/src/mindustry/content/SectorPresets.java
index ff1f36bc22..e516bab740 100644
--- a/core/src/mindustry/content/SectorPresets.java
+++ b/core/src/mindustry/content/SectorPresets.java
@@ -18,7 +18,7 @@ public class SectorPresets implements ContentList{
groundZero = new SectorPreset("groundZero", serpulo, 15){{
alwaysUnlocked = true;
captureWave = 10;
- difficulty = 0;
+ difficulty = 1;
}};
saltFlats = new SectorPreset("saltFlats", serpulo, 101){{
@@ -26,23 +26,23 @@ public class SectorPresets implements ContentList{
}};
frozenForest = new SectorPreset("frozenForest", serpulo, 86){{
- captureWave = 40;
- difficulty = 1;
+ captureWave = 20;
+ difficulty = 2;
}};
craters = new SectorPreset("craters", serpulo, 18){{
- captureWave = 40;
+ captureWave = 20;
difficulty = 2;
}};
ruinousShores = new SectorPreset("ruinousShores", serpulo, 19){{
- captureWave = 40;
+ captureWave = 30;
difficulty = 3;
}};
stainedMountains = new SectorPreset("stainedMountains", serpulo, 20){{
captureWave = 30;
- difficulty = 2;
+ difficulty = 3;
}};
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
@@ -54,7 +54,7 @@ public class SectorPresets implements ContentList{
}};
tarFields = new SectorPreset("tarFields", serpulo, 23){{
- captureWave = 40;
+ captureWave = 50;
difficulty = 5;
}};
diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java
index 9a1b851118..b9189ec64e 100644
--- a/core/src/mindustry/content/UnitTypes.java
+++ b/core/src/mindustry/content/UnitTypes.java
@@ -872,7 +872,6 @@ public class UnitTypes implements ContentList{
drag = 0.01f;
flying = true;
health = 75;
- faceTarget = false;
engineOffset = 5.5f;
range = 140f;
@@ -1449,13 +1448,13 @@ public class UnitTypes implements ContentList{
trailMult = 0.8f;
hitEffect = Fx.massiveExplosion;
knockback = 1.5f;
- lifetime = 140f;
+ lifetime = 100f;
height = 15.5f;
width = 15f;
collidesTiles = false;
ammoMultiplier = 4f;
splashDamageRadius = 60f;
- splashDamage = 85f;
+ splashDamage = 80f;
backColor = Pal.missileYellowBack;
frontColor = Pal.missileYellow;
trailEffect = Fx.artilleryTrail;
diff --git a/core/src/mindustry/core/Control.java b/core/src/mindustry/core/Control.java
index efe2327074..e24881692f 100644
--- a/core/src/mindustry/core/Control.java
+++ b/core/src/mindustry/core/Control.java
@@ -9,7 +9,6 @@ import arc.math.*;
import arc.scene.ui.*;
import arc.struct.*;
import arc.util.*;
-import mindustry.*;
import mindustry.audio.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
@@ -25,7 +24,6 @@ import mindustry.maps.Map;
import mindustry.type.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
-import mindustry.world.blocks.storage.CoreBlock.*;
import java.io.*;
import java.text.*;
@@ -160,9 +158,7 @@ public class Control implements ApplicationListener, Loadable{
//delete the save, it is gone.
if(saves.getCurrent() != null && !state.rules.tutorial){
- Sector sector = state.getSector();
- sector.save = null;
- saves.getCurrent().delete();
+ saves.getCurrent().save();
}
}
});
@@ -252,19 +248,6 @@ public class Control implements ApplicationListener, Loadable{
});
}
- //TODO move
- public void handleLaunch(CoreBuild tile){
- LaunchCorec ent = LaunchCore.create();
- ent.set(tile);
- ent.block(Blocks.coreShard);
- ent.lifetime(Vars.launchDuration);
- ent.add();
-
- //remove schematic requirements from core
- tile.items.remove(universe.getLastLoadout().requirements());
- tile.items.remove(universe.getLaunchResources());
- }
-
public void playSector(Sector sector){
playSector(sector, sector);
}
@@ -281,21 +264,29 @@ public class Control implements ApplicationListener, Loadable{
slot.load();
slot.setAutosave(true);
state.rules.sector = sector;
+ state.secinfo = state.rules.sector.info;
//if there is no base, simulate a new game and place the right loadout at the spawn position
- //TODO this is broken?
if(state.rules.defaultTeam.cores().isEmpty()){
- //kill all friendly units, since they should be dead anwyay
- for(Unit unit : Groups.unit){
- if(unit.team() == state.rules.defaultTeam){
- unit.remove();
- }
+ //no spawn set -> delete the sector save
+ if(sector.info.spawnPosition == 0){
+ //delete old save
+ sector.save = null;
+ slot.delete();
+ //play again
+ playSector(origin, sector);
+ return;
}
- Tile spawn = world.tile(sector.getSpawnPosition());
- //TODO PLACE CORRECT LOADOUT
- Schematics.placeLoadout(universe.getLastLoadout(), spawn.x, spawn.y);
+ //reset wave so things are more fair
+ state.wave = 1;
+
+ //kill all units, since they should be dead anwyay
+ Groups.unit.clear();
+
+ Tile spawn = world.tile(sector.info.spawnPosition);
+ Schematics.placeLaunchLoadout(spawn.x, spawn.y);
//set up camera/player locations
player.set(spawn.x * tilesize, spawn.y * tilesize);
@@ -317,7 +308,6 @@ public class Control implements ApplicationListener, Loadable{
}else{
net.reset();
logic.reset();
- sector.setSecondsPassed(0);
world.loadSector(sector);
state.rules.sector = sector;
//assign origin when launching
diff --git a/core/src/mindustry/core/GameState.java b/core/src/mindustry/core/GameState.java
index 949e40d02d..7d39da5176 100644
--- a/core/src/mindustry/core/GameState.java
+++ b/core/src/mindustry/core/GameState.java
@@ -41,10 +41,17 @@ public class GameState{
}
public void set(State astate){
+ //cannot pause when in multiplayer
+ if(astate == State.paused && net.active()) return;
+
Events.fire(new StateChangeEvent(state, astate));
state = astate;
}
+ public boolean hasSpawns(){
+ return rules.waves && !(isCampaign() && rules.attackMode);
+ }
+
/** Note that being in a campaign does not necessarily mean having a sector. */
public boolean isCampaign(){
return rules.sector != null;
@@ -68,7 +75,7 @@ public class GameState{
}
public boolean isPlaying(){
- return state == State.playing;
+ return (state == State.playing) || (state == State.paused && !isPaused());
}
/** @return whether the current state is *not* the menu. */
diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java
index df536552df..a96ea101a9 100644
--- a/core/src/mindustry/core/Logic.java
+++ b/core/src/mindustry/core/Logic.java
@@ -5,16 +5,15 @@ 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.*;
-import mindustry.world.blocks.*;
-import mindustry.world.blocks.ConstructBlock.*;
-import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
@@ -39,32 +38,7 @@ public class Logic implements ApplicationListener{
//skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests
if(tile.build == null || !tile.block().rebuildable || net.client()) return;
- if(block instanceof ConstructBlock){
-
- ConstructBuild entity = tile.bc();
-
- //update block to reflect the fact that something was being constructed
- if(entity.cblock != null && entity.cblock.synthetic()){
- block = entity.cblock;
- }else{
- //otherwise this was a deconstruction that was interrupted, don't want to rebuild that
- return;
- }
- }
-
- TeamData data = state.teams.get(tile.team());
-
- //remove existing blocks that have been placed here.
- //painful O(n) iteration + copy
- for(int i = 0; i < data.blocks.size; i++){
- BlockPlan b = data.blocks.get(i);
- if(b.x == tile.x && b.y == tile.y){
- data.blocks.removeIndex(i);
- break;
- }
- }
-
- data.blocks.addFirst(new BlockPlan(tile.x, tile.y, (short)tile.build.rotation, block.id, tile.build.config()));
+ tile.build.addPlan(true);
});
Events.on(BlockBuildEndEvent.class, event -> {
@@ -84,51 +58,61 @@ 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;
+ state.secinfo.write();
- //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.secinfo.wavesPassed;
- //TODO sector damage disabled for now
- //if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
- // SectorDamage.apply(turnsPassed / sectorDestructionTurns);
- //}
-
- //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);
- }
+ //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();
+ }
+ });
}
- 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
state.rules.waveTeam.rules().infiniteAmmo = true;
+ if(state.isCampaign()){
+ //enable building AI
+ state.rules.waveTeam.rules().ai = true;
+ state.rules.waveTeam.rules().infiniteResources = true;
+ }
//save settings
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. */
@@ -168,11 +152,6 @@ public class Logic implements ApplicationListener{
}
public void skipWave(){
- if(state.isCampaign()){
- //warp time spent forward because the wave was just skipped.
- state.secinfo.internalTimeSpent += state.wavetime;
- }
-
state.wavetime = 0;
}
@@ -199,12 +178,13 @@ 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()){
+ if(state.rules.waves && (state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()) ||
+ (state.rules.attackMode && state.rules.waveTeam.cores().isEmpty())){
//the sector has been conquered - waves get disabled
state.rules.waves = false;
+ //disable attack mode
+ state.rules.attackMode = false;
//fire capture event
Events.fire(new SectorCaptureEvent(state.rules.sector));
@@ -213,7 +193,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 +246,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
@@ -283,7 +272,7 @@ public class Logic implements ApplicationListener{
if(state.isGame()){
if(!net.client()){
- state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type().isCounted);
+ state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type.isCounted);
}
if(!state.isPaused()){
diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java
index 77ab732cf6..d9f944c1aa 100644
--- a/core/src/mindustry/core/NetServer.java
+++ b/core/src/mindustry/core/NetServer.java
@@ -575,7 +575,7 @@ public class NetServer implements ApplicationListener{
shooting = false;
}
- if(!player.dead() && (player.unit().type().flying || !player.unit().type().canBoost)){
+ if(!player.dead() && (player.unit().type.flying || !player.unit().type.canBoost)){
boosting = false;
}
@@ -629,7 +629,7 @@ public class NetServer implements ApplicationListener{
Unit unit = player.unit();
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
- float maxSpeed = ((player.unit().type().canBoost && player.unit().isFlying()) ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
+ float maxSpeed = ((player.unit().type.canBoost && player.unit().isFlying()) ? player.unit().type.boostMultiplier : 1f) * player.unit().type.speed;
if(unit.isGrounded()){
maxSpeed *= unit.floorSpeedMultiplier();
}
diff --git a/core/src/mindustry/core/World.java b/core/src/mindustry/core/World.java
index 03a18c01ed..41472410f4 100644
--- a/core/src/mindustry/core/World.java
+++ b/core/src/mindustry/core/World.java
@@ -148,7 +148,17 @@ public class World{
return build(Math.round(x / tilesize), Math.round(y / tilesize));
}
- public int toTile(float coord){
+ /** Convert from world to logic tile coordinates. Whole numbers are at centers of tiles. */
+ public static float conv(float coord){
+ return coord / tilesize;
+ }
+
+ /** Convert from tile to world coordinates. */
+ public static float unconv(float coord){
+ return coord * tilesize;
+ }
+
+ public static int toTile(float coord){
return Math.round(coord / tilesize);
}
@@ -253,7 +263,7 @@ public class World{
setSectorRules(sector);
if(state.rules.defaultTeam.core() != null){
- sector.setSpawnPosition(state.rules.defaultTeam.core().pos());
+ sector.info.spawnPosition = state.rules.defaultTeam.core().pos();
}
}
@@ -267,8 +277,6 @@ public class World{
ObjectIntMap floorc = new ObjectIntMap<>();
ObjectSet content = new ObjectSet<>();
- float waterFloors = 0, totalFloors = 0;
-
for(Tile tile : world.tiles){
if(world.getDarkness(tile.x, tile.y) >= 3){
continue;
@@ -280,10 +288,6 @@ public class World{
if(liquid != null) content.add(liquid);
if(!tile.block().isStatic()){
- totalFloors ++;
- if(liquid == Liquids.water){
- waterFloors += tile.floor().isDeep() ? 1f : 0.7f;
- }
floorc.increment(tile.floor());
if(tile.overlay() != Blocks.air){
floorc.increment(tile.overlay());
@@ -326,9 +330,9 @@ public class World{
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
}
- state.secinfo.resources = content.asArray();
- state.secinfo.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
-
+ sector.info.resources = content.asArray();
+ sector.info.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
+ sector.saveInfo();
}
public Context filterContext(Map map){
diff --git a/core/src/mindustry/ctype/UnlockableContent.java b/core/src/mindustry/ctype/UnlockableContent.java
index f6e6d0a2dc..21b72374ec 100644
--- a/core/src/mindustry/ctype/UnlockableContent.java
+++ b/core/src/mindustry/ctype/UnlockableContent.java
@@ -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();
}
}
diff --git a/core/src/mindustry/editor/EditorTile.java b/core/src/mindustry/editor/EditorTile.java
index 5ff533325d..2d08b9d3d2 100644
--- a/core/src/mindustry/editor/EditorTile.java
+++ b/core/src/mindustry/editor/EditorTile.java
@@ -105,9 +105,9 @@ public class EditorTile extends Tile{
}
@Override
- protected void changeEntity(Team team, Prov entityprov, int rotation){
+ protected void changeBuild(Team team, Prov entityprov, int rotation){
if(skip()){
- super.changeEntity(team, entityprov, rotation);
+ super.changeBuild(team, entityprov, rotation);
return;
}
diff --git a/core/src/mindustry/editor/MapEditor.java b/core/src/mindustry/editor/MapEditor.java
index 49d174779b..1c33964e08 100644
--- a/core/src/mindustry/editor/MapEditor.java
+++ b/core/src/mindustry/editor/MapEditor.java
@@ -4,6 +4,7 @@ import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
+import arc.math.geom.*;
import arc.struct.*;
import mindustry.content.*;
import mindustry.editor.DrawOperation.*;
@@ -180,6 +181,52 @@ public class MapEditor{
return false;
}
+ public void addCliffs(){
+ for(Tile tile : world.tiles){
+ if(!tile.block().isStatic() || tile.block() == Blocks.cliff) continue;
+
+ int rotation = 0;
+ for(int i = 0; i < 8; i++){
+ Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
+ if(other != null && !other.block().isStatic()){
+ rotation |= (1 << i);
+ }
+ }
+
+ if(rotation != 0){
+ tile.setBlock(Blocks.cliff);
+ }
+
+ tile.data = (byte)rotation;
+ }
+
+ for(Tile tile : world.tiles){
+ if(tile.block() != Blocks.cliff && tile.block().isStatic()){
+ tile.setBlock(Blocks.air);
+ }
+ }
+ }
+
+ public void addFloorCliffs(){
+ for(Tile tile : world.tiles){
+ if(!tile.floor().hasSurface() || tile.block() == Blocks.cliff) continue;
+
+ int rotation = 0;
+ for(int i = 0; i < 8; i++){
+ Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
+ if(other != null && !other.floor().hasSurface()){
+ rotation |= (1 << i);
+ }
+ }
+
+ if(rotation != 0){
+ tile.setBlock(Blocks.cliff);
+ }
+
+ tile.data = (byte)rotation;
+ }
+ }
+
public void drawCircle(int x, int y, Cons drawer){
for(int rx = -brushSize; rx <= brushSize; rx++){
for(int ry = -brushSize; ry <= brushSize; ry++){
diff --git a/core/src/mindustry/editor/MapEditorDialog.java b/core/src/mindustry/editor/MapEditorDialog.java
index 4f7eb008fd..dfad7c03ab 100644
--- a/core/src/mindustry/editor/MapEditorDialog.java
+++ b/core/src/mindustry/editor/MapEditorDialog.java
@@ -385,7 +385,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
public void build(){
- float size = 60f;
+ float size = 58f;
clearChildren();
table(cont -> {
@@ -559,10 +559,19 @@ public class MapEditorDialog extends Dialog implements Disposable{
mid.row();
- mid.table(t -> {
- t.button("@editor.center", Icon.move, Styles.cleart, () -> view.center()).growX().margin(9f);
- }).growX().top();
+ if(!mobile){
+ mid.table(t -> {
+ t.button("@editor.center", Icon.move, Styles.cleart, view::center).growX().margin(9f);
+ }).growX().top();
+ }
+ if(addCliffButton){
+ mid.row();
+
+ mid.table(t -> {
+ t.button("Cliffs", Icon.terrain, Styles.cleart, editor::addCliffs).growX().margin(9f);
+ }).growX().top();
+ }
}).margin(0).left().growY();
diff --git a/core/src/mindustry/editor/MapGenerateDialog.java b/core/src/mindustry/editor/MapGenerateDialog.java
index fae31dc7d5..6a06e0b966 100644
--- a/core/src/mindustry/editor/MapGenerateDialog.java
+++ b/core/src/mindustry/editor/MapGenerateDialog.java
@@ -51,7 +51,7 @@ public class MapGenerateDialog extends BaseDialog{
CachedTile ctile = new CachedTile(){
//nothing.
@Override
- protected void changeEntity(Team team, Prov entityprov, int rotation){
+ protected void changeBuild(Team team, Prov entityprov, int rotation){
}
};
diff --git a/core/src/mindustry/editor/WaveGraph.java b/core/src/mindustry/editor/WaveGraph.java
index 14b2416085..2436c62c2a 100644
--- a/core/src/mindustry/editor/WaveGraph.java
+++ b/core/src/mindustry/editor/WaveGraph.java
@@ -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);
diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java
index e22891eba5..b6c9abe807 100644
--- a/core/src/mindustry/entities/Damage.java
+++ b/core/src/mindustry/entities/Damage.java
@@ -9,6 +9,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -82,7 +83,7 @@ public class Damage{
furthest = null;
- boolean found = world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
+ boolean found = world.raycast(b.tileX(), b.tileY(), World.toTile(b.x + Tmp.v1.x), World.toTile(b.y + Tmp.v1.y),
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
diff --git a/core/src/mindustry/entities/Lightning.java b/core/src/mindustry/entities/Lightning.java
index 738bfa1f1e..2d647bfb1e 100644
--- a/core/src/mindustry/entities/Lightning.java
+++ b/core/src/mindustry/entities/Lightning.java
@@ -5,6 +5,7 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.entities.bullet.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -48,7 +49,7 @@ public class Lightning{
bhit = false;
Vec2 from = lines.get(lines.size - 2);
Vec2 to = lines.get(lines.size - 1);
- world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
+ world.raycastEach(World.toTile(from.getX()), World.toTile(from.getY()), World.toTile(to.getX()), World.toTile(to.getY()), (wx, wy) -> {
Tile tile = world.tile(wx, wy);
if(tile != null && tile.block().insulated){
diff --git a/core/src/mindustry/entities/Units.java b/core/src/mindustry/entities/Units.java
index 1babb75e33..e4591a0ed0 100644
--- a/core/src/mindustry/entities/Units.java
+++ b/core/src/mindustry/entities/Units.java
@@ -126,7 +126,7 @@ public class Units{
nearby(x, y, width, height, unit -> {
if(boolResult) return;
- if((unit.isGrounded() && !unit.type().hovering) == ground){
+ if((unit.isGrounded() && !unit.type.hovering) == ground){
unit.hitbox(hitrect);
if(hitrect.overlaps(x, y, width, height)){
diff --git a/core/src/mindustry/entities/abilities/ForceFieldAbility.java b/core/src/mindustry/entities/abilities/ForceFieldAbility.java
index fa9fa5e2c8..d341191a74 100644
--- a/core/src/mindustry/entities/abilities/ForceFieldAbility.java
+++ b/core/src/mindustry/entities/abilities/ForceFieldAbility.java
@@ -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
realRad = radiusScale * radius;
}
diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java
index 0c3a605f1b..8e06cf466e 100644
--- a/core/src/mindustry/entities/bullet/BulletType.java
+++ b/core/src/mindustry/entities/bullet/BulletType.java
@@ -104,6 +104,8 @@ public abstract class BulletType extends Content{
public float incendChance = 1f;
public float homingPower = 0f;
public float homingRange = 50f;
+ /** Use a negative value to disable homing delay. */
+ public float homingDelay = -1f;
public Color lightningColor = Pal.surge;
public int lightning;
@@ -137,6 +139,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);
@@ -251,7 +262,7 @@ public abstract class BulletType extends Content{
}
public void update(Bullet b){
- if(homingPower > 0.0001f){
+ if(homingPower > 0.0001f && b.time >= homingDelay){
Teamc target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> (e.isGrounded() && collidesGround) || (e.isFlying() && collidesAir), t -> collidesGround);
if(target != null){
b.vel.setAngle(Mathf.slerpDelta(b.rotation(), b.angleTo(target), homingPower));
diff --git a/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java b/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java
index a862671040..4c52105b26 100644
--- a/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java
+++ b/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java
@@ -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;
diff --git a/core/src/mindustry/entities/bullet/LaserBulletType.java b/core/src/mindustry/entities/bullet/LaserBulletType.java
index ad5965f3b8..6d43baf95c 100644
--- a/core/src/mindustry/entities/bullet/LaserBulletType.java
+++ b/core/src/mindustry/entities/bullet/LaserBulletType.java
@@ -39,6 +39,11 @@ public class LaserBulletType extends BulletType{
this(1f);
}
+ @Override
+ public float estimateDPS(){
+ return super.estimateDPS() * 2f;
+ }
+
@Override
public void init(){
super.init();
diff --git a/core/src/mindustry/entities/bullet/LiquidBulletType.java b/core/src/mindustry/entities/bullet/LiquidBulletType.java
index 2d3de79977..5da33f95e0 100644
--- a/core/src/mindustry/entities/bullet/LiquidBulletType.java
+++ b/core/src/mindustry/entities/bullet/LiquidBulletType.java
@@ -22,6 +22,8 @@ public class LiquidBulletType extends BulletType{
if(liquid != null){
this.liquid = liquid;
this.status = liquid.effect;
+ lightColor = liquid.lightColor;
+ lightOpacity = liquid.lightColor.a;
}
ammoMultiplier = 1f;
diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java
index 15d79a8942..45b1042660 100644
--- a/core/src/mindustry/entities/comp/BuildingComp.java
+++ b/core/src/mindustry/entities/comp/BuildingComp.java
@@ -17,16 +17,19 @@ import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.audio.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
+import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
+import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.power.*;
@@ -191,6 +194,36 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region utility methods
+ public void addPlan(boolean checkPrevious){
+ if(!block.rebuildable) return;
+
+ if(self() instanceof ConstructBuild entity){
+ //update block to reflect the fact that something was being constructed
+ if(entity.cblock != null && entity.cblock.synthetic()){
+ block = entity.cblock;
+ }else{
+ //otherwise this was a deconstruction that was interrupted, don't want to rebuild that
+ return;
+ }
+ }
+
+ TeamData data = state.teams.get(team);
+
+ if(checkPrevious){
+ //remove existing blocks that have been placed here.
+ //painful O(n) iteration + copy
+ for(int i = 0; i < data.blocks.size; i++){
+ BlockPlan b = data.blocks.get(i);
+ if(b.x == tile.x && b.y == tile.y){
+ data.blocks.removeIndex(i);
+ break;
+ }
+ }
+ }
+
+ data.blocks.addFirst(new BlockPlan(tile.x, tile.y, (short)rotation, block.id, config()));
+ }
+
/** Configure with the current, local player. */
public void configure(Object value){
//save last used config
@@ -431,7 +464,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
*/
public boolean movePayload(Payload todump){
int trns = block.size/2 + 1;
- Tile next = tile.getNearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
+ Tile next = tile.nearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(self(), todump)){
next.build.handlePayload(self(), todump);
@@ -514,7 +547,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public float moveLiquidForward(boolean leaks, Liquid liquid){
- Tile next = tile.getNearby(rotation);
+ Tile next = tile.nearby(rotation);
if(next == null) return 0;
@@ -766,9 +799,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void drawCracks(){
- if(!damaged() || block.size > Block.maxCrackSize) return;
+ if(!damaged() || block.size > BlockRenderer.maxCrackSize) return;
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.rect(region, x, y, (id%4)*90);
Draw.color();
@@ -1234,8 +1267,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Override
public double sense(LAccess sensor){
return switch(sensor){
- case x -> x;
- case y -> y;
+ case x -> World.conv(x);
+ case y -> World.conv(y);
case team -> team.id;
case health -> health;
case maxHealth -> maxHealth;
@@ -1263,7 +1296,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
case type -> block;
case firstItem -> items == null ? null : items.first();
case config -> block.configurations.containsKey(Item.class) || block.configurations.containsKey(Liquid.class) ? config() : null;
- case payloadType -> getPayload() instanceof UnitPayload p1 ? p1.unit.type() : getPayload() instanceof BuildPayload p2 ? p2.block() : null;
+ case payloadType -> getPayload() instanceof UnitPayload p1 ? p1.unit.type : getPayload() instanceof BuildPayload p2 ? p2.block() : null;
default -> noSensed;
};
diff --git a/core/src/mindustry/entities/comp/BulletComp.java b/core/src/mindustry/entities/comp/BulletComp.java
index d9aa007c5e..5a08710762 100644
--- a/core/src/mindustry/entities/comp/BulletComp.java
+++ b/core/src/mindustry/entities/comp/BulletComp.java
@@ -7,6 +7,7 @@ import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
+import mindustry.core.*;
import mindustry.entities.bullet.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
@@ -110,7 +111,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
type.update(self());
if(type.collidesTiles && type.collides && type.collidesGround){
- world.raycastEach(world.toTile(lastX()), world.toTile(lastY()), tileX(), tileY(), (x, y) -> {
+ world.raycastEach(World.toTile(lastX()), World.toTile(lastY()), tileX(), tileY(), (x, y) -> {
Building tile = world.build(x, y);
if(tile == null || !isAdded()) return false;
diff --git a/core/src/mindustry/entities/comp/CommanderComp.java b/core/src/mindustry/entities/comp/CommanderComp.java
index b0fbddcf85..e03147d63c 100644
--- a/core/src/mindustry/entities/comp/CommanderComp.java
+++ b/core/src/mindustry/entities/comp/CommanderComp.java
@@ -59,7 +59,7 @@ abstract class CommanderComp implements Entityc, Posc{
units.clear();
Units.nearby(team, x, y, 150f, u -> {
- if(u.isAI() && include.get(u) && u != self() && u.type().flying == type.flying && u.hitSize <= hitSize * 1.1f){
+ if(u.isAI() && include.get(u) && u != self() && u.type.flying == type.flying && u.hitSize <= hitSize * 1.1f){
units.add(u);
}
});
@@ -82,7 +82,7 @@ abstract class CommanderComp implements Entityc, Posc{
FormationAI ai;
unit.controller(ai = new FormationAI(self(), formation));
spacing = Math.max(spacing, ai.formationSize());
- minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
+ minFormationSpeed = Math.min(minFormationSpeed, unit.type.speed);
}
this.formation = formation;
@@ -106,7 +106,7 @@ abstract class CommanderComp implements Entityc, Posc{
//reset controlled units
for(Unit unit : controlling){
if(unit.controller().isBeingControlled(self())){
- unit.controller(unit.type().createController());
+ unit.controller(unit.type.createController());
}
}
diff --git a/core/src/mindustry/entities/comp/FireComp.java b/core/src/mindustry/entities/comp/FireComp.java
index ea67ca9e19..84fcb6b3b5 100644
--- a/core/src/mindustry/entities/comp/FireComp.java
+++ b/core/src/mindustry/entities/comp/FireComp.java
@@ -16,7 +16,7 @@ import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component(base = true)
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
- private static final float spreadChance = 0.05f, fireballChance = 0.07f;
+ private static final float spreadChance = 0.04f, fireballChance = 0.06f;
@Import float time, lifetime, x, y;
diff --git a/core/src/mindustry/entities/comp/PayloadComp.java b/core/src/mindustry/entities/comp/PayloadComp.java
index ac1f4fa323..e2945d08f7 100644
--- a/core/src/mindustry/entities/comp/PayloadComp.java
+++ b/core/src/mindustry/entities/comp/PayloadComp.java
@@ -6,6 +6,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
@@ -120,7 +121,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
/** @return whether the tile has been successfully placed. */
boolean dropBlock(BuildPayload payload){
Building tile = payload.build;
- int tx = Vars.world.toTile(x - tile.block.offset), ty = Vars.world.toTile(y - tile.block.offset);
+ int tx = World.toTile(x - tile.block.offset), ty = World.toTile(y - tile.block.offset);
Tile on = Vars.world.tile(tx, ty);
if(on != null && Build.validPlace(tile.block, tile.team, tx, ty, tile.rotation, false)){
int rot = (int)((rotation + 45f) / 90f) % 4;
diff --git a/core/src/mindustry/entities/comp/PlayerComp.java b/core/src/mindustry/entities/comp/PlayerComp.java
index 4ee481b032..736a1b68e5 100644
--- a/core/src/mindustry/entities/comp/PlayerComp.java
+++ b/core/src/mindustry/entities/comp/PlayerComp.java
@@ -79,7 +79,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
admin = typing = false;
textFadeTime = 0f;
if(!dead()){
- unit.controller(unit.type().createController());
+ unit.controller(unit.type.createController());
unit = Nulls.unit;
}
}
@@ -91,7 +91,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Replace
public float clipSize(){
- return unit.isNull() ? 20 : unit.type().hitSize * 2f;
+ return unit.isNull() ? 20 : unit.type.hitSize * 2f;
}
@Override
@@ -123,7 +123,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
deathTimer = 0;
//update some basic state to sync things
- if(unit.type().canBoost){
+ if(unit.type.canBoost){
Tile tile = unit.tileOn();
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f);
}
@@ -177,7 +177,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
if(this.unit != Nulls.unit){
//un-control the old unit
- this.unit.controller(this.unit.type().createController());
+ this.unit.controller(this.unit.type.createController());
}
this.unit = unit;
if(unit != Nulls.unit){
diff --git a/core/src/mindustry/entities/comp/PosComp.java b/core/src/mindustry/entities/comp/PosComp.java
index 15c665739e..6446c0f91f 100644
--- a/core/src/mindustry/entities/comp/PosComp.java
+++ b/core/src/mindustry/entities/comp/PosComp.java
@@ -2,9 +2,9 @@ package mindustry.entities.comp;
import arc.math.geom.*;
import arc.util.*;
-import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
@@ -32,11 +32,11 @@ abstract class PosComp implements Position{
}
int tileX(){
- return Vars.world.toTile(x);
+ return World.toTile(x);
}
int tileY(){
- return Vars.world.toTile(y);
+ return World.toTile(y);
}
/** Returns air if this unit is on a non-air top block. */
diff --git a/core/src/mindustry/entities/comp/PuddleComp.java b/core/src/mindustry/entities/comp/PuddleComp.java
index 669ee7e1f2..d83408c734 100644
--- a/core/src/mindustry/entities/comp/PuddleComp.java
+++ b/core/src/mindustry/entities/comp/PuddleComp.java
@@ -74,7 +74,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
unit.apply(liquid.effect, 60 * 2);
if(unit.vel.len() > 0.1){
- Fx.ripple.at(unit.x, unit.y, unit.type().rippleScale, liquid.color);
+ Fx.ripple.at(unit.x, unit.y, unit.type.rippleScale, liquid.color);
}
}
}
diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java
index 1cf0f7e216..0a703366e9 100644
--- a/core/src/mindustry/entities/comp/UnitComp.java
+++ b/core/src/mindustry/entities/comp/UnitComp.java
@@ -10,6 +10,7 @@ import arc.util.*;
import mindustry.ai.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.abilities.*;
@@ -36,7 +37,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Import int id;
private UnitController controller;
- private UnitType type;
+ UnitType type;
boolean spawnedByCore;
double flag;
@@ -88,12 +89,14 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case rotation -> rotation;
case health -> health;
case maxHealth -> maxHealth;
- case x -> x;
- case y -> y;
+ case ammo -> state.rules.unitAmmo ? type.ammoCapacity : ammo;
+ case ammoCapacity -> type.ammoCapacity;
+ case x -> World.conv(x);
+ case y -> World.conv(y);
case team -> team.id;
case shooting -> isShooting() ? 1 : 0;
- case shootX -> aimX();
- case shootY -> aimY();
+ case shootX -> World.conv(aimX());
+ case shootY -> World.conv(aimY());
case flag -> flag;
case payloadCount -> self() instanceof Payloadc pay ? pay.payloads().size : 0;
default -> 0;
@@ -108,7 +111,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case firstItem -> stack().amount == 0 ? null : item();
case payloadType -> self() instanceof Payloadc pay ?
(pay.payloads().isEmpty() ? null :
- pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type() :
+ pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type :
pay.payloads().peek() instanceof BuildPayload p2 ? p2.block() : null) : null;
default -> noSensed;
};
@@ -161,22 +164,12 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void set(UnitType def, UnitController controller){
- type(type);
+ if(this.type != def){
+ setType(def);
+ }
controller(controller);
}
- @Override
- public void type(UnitType type){
- if(this.type == type) return;
-
- setStats(type);
- }
-
- @Override
- public UnitType type(){
- return type;
- }
-
/** @return pathfinder path type for calculating costs */
public int pathType(){
return Pathfinder.costGround;
@@ -206,7 +199,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return Units.getCap(team);
}
- public void setStats(UnitType type){
+ public void setType(UnitType type){
this.type = type;
this.maxHealth = type.health;
this.drag = type.drag;
@@ -224,7 +217,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void afterSync(){
//set up type info after reading
- setStats(this.type);
+ setType(this.type);
controller.unit(self());
}
@@ -284,7 +277,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
drag = type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f);
//apply knockback based on spawns
- if(team != state.rules.waveTeam){
+ if(team != state.rules.waveTeam && state.hasSpawns()){
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
for(Tile spawn : spawner.getSpawns()){
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
diff --git a/core/src/mindustry/entities/comp/WeaponsComp.java b/core/src/mindustry/entities/comp/WeaponsComp.java
index d891dbc1e4..dec0105c14 100644
--- a/core/src/mindustry/entities/comp/WeaponsComp.java
+++ b/core/src/mindustry/entities/comp/WeaponsComp.java
@@ -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));
diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java
index b02fa5dc00..ca005fe25d 100644
--- a/core/src/mindustry/entities/units/AIController.java
+++ b/core/src/mindustry/entities/units/AIController.java
@@ -95,7 +95,7 @@ public class AIController implements UnitController{
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
- unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
+ unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type.speed));
}
protected void updateWeapons(){
@@ -105,7 +105,7 @@ public class AIController implements UnitController{
boolean ret = retarget();
if(ret){
- target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
+ target = findTarget(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
}
if(invalid(target)){
@@ -119,7 +119,7 @@ public class AIController implements UnitController{
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
- if(unit.type().singleTarget){
+ if(unit.type.singleTarget){
targets[i] = target;
}else{
if(ret){
@@ -176,7 +176,7 @@ public class AIController implements UnitController{
}
protected void circle(Position target, float circleLength){
- circle(target, circleLength, unit.type().speed);
+ circle(target, circleLength, unit.type.speed);
}
protected void circle(Position target, float circleLength, float speed){
diff --git a/core/src/mindustry/game/DefaultWaves.java b/core/src/mindustry/game/DefaultWaves.java
index 0ce0d3a6a2..d33af5a41a 100644
--- a/core/src/mindustry/game/DefaultWaves.java
+++ b/core/src/mindustry/game/DefaultWaves.java
@@ -18,6 +18,7 @@ public class DefaultWaves{
new SpawnGroup(dagger){{
end = 10;
unitScaling = 2f;
+ max = 30;
}},
new SpawnGroup(crawler){{
@@ -45,6 +46,7 @@ public class DefaultWaves{
begin = 13;
spacing = 3;
unitScaling = 0.5f;
+ max = 25;
}},
new SpawnGroup(mace){{
@@ -61,7 +63,7 @@ public class DefaultWaves{
unitAmount = 4;
spacing = 2;
shieldScaling = 10f;
- max = 20;
+ max = 14;
}},
new SpawnGroup(mace){{
@@ -81,7 +83,7 @@ public class DefaultWaves{
effect = StatusEffects.overdrive;
}},
- new SpawnGroup(mace){{
+ new SpawnGroup(pulsar){{
begin = 120;
spacing = 2;
unitScaling = 3;
@@ -94,6 +96,7 @@ public class DefaultWaves{
unitScaling = 1;
spacing = 2;
shieldScaling = 20f;
+ max = 20;
}},
new SpawnGroup(quasar){{
@@ -111,6 +114,7 @@ public class DefaultWaves{
unitAmount = 1;
unitScaling = 3;
effect = StatusEffects.shielded;
+ max = 25;
}},
new SpawnGroup(fortress){{
@@ -122,7 +126,7 @@ public class DefaultWaves{
shieldScaling = 30;
}},
- new SpawnGroup(dagger){{
+ new SpawnGroup(nova){{
begin = 35;
spacing = 3;
unitAmount = 4;
@@ -138,6 +142,7 @@ public class DefaultWaves{
effect = StatusEffects.overdrive;
items = new ItemStack(Items.pyratite, 100);
end = 130;
+ max = 30;
}},
new SpawnGroup(horizon){{
@@ -156,6 +161,7 @@ public class DefaultWaves{
shields = 100f;
shieldScaling = 10f;
effect = StatusEffects.overdrive;
+ max = 20;
}},
new SpawnGroup(zenith){{
@@ -233,7 +239,7 @@ public class DefaultWaves{
shieldScaling = 20f;
}},
- new SpawnGroup(atrax){{
+ new SpawnGroup(toxopid){{
begin = 210;
unitAmount = 1;
unitScaling = 1;
@@ -258,7 +264,7 @@ public class DefaultWaves{
{nova, pulsar, quasar, vela, corvus},
{crawler, atrax, spiroct, arkyid, toxopid},
//{risso, minke, bryde, sei, omura}, //questionable choices
- //{mono, poly, mega, quad, oct}, //do not attack
+ {poly, poly, mega, quad, quad},
{flare, horizon, zenith, antumbra, eclipse}
};
@@ -290,7 +296,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 +335,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));
diff --git a/core/src/mindustry/game/EventType.java b/core/src/mindustry/game/EventType.java
index 272c26363f..1ab946c4ac 100644
--- a/core/src/mindustry/game/EventType.java
+++ b/core/src/mindustry/game/EventType.java
@@ -35,7 +35,13 @@ public class EventType{
preDraw,
postDraw,
uiDrawBegin,
- uiDrawEnd
+ uiDrawEnd,
+ //before/after bloom used, skybox or planets drawn
+ universeDrawBegin,
+ //skybox drawn and bloom is enabled - use Vars.renderer.planets
+ universeDraw,
+ //planets drawn and bloom disabled
+ universeDrawEnd
}
public static class WinEvent{}
@@ -73,6 +79,15 @@ public class EventType{
}
}
+ /** Called when a sector is destroyed by waves when you're not there. */
+ public static class SectorInvasionEvent{
+ public final Sector sector;
+
+ public SectorInvasionEvent(Sector sector){
+ this.sector = sector;
+ }
+ }
+
public static class LaunchItemEvent{
public final ItemStack stack;
@@ -214,8 +229,8 @@ public class EventType{
}
/**
- * Called when block building begins by placing down the BuildBlock.
- * The tile's block will nearly always be a BuildBlock.
+ * Called when block building begins by placing down the ConstructBlock.
+ * The tile's block will nearly always be a ConstructBlock.
*/
public static class BlockBuildBeginEvent{
public final Tile tile;
@@ -247,7 +262,7 @@ public class EventType{
/**
* Called when a player or drone begins building something.
- * This does not necessarily happen when a new BuildBlock is created.
+ * This does not necessarily happen when a new ConstructBlock is created.
*/
public static class BuildSelectEvent{
public final Tile tile;
diff --git a/core/src/mindustry/game/Gamemode.java b/core/src/mindustry/game/Gamemode.java
index 9de5e71052..f2bb95130a 100644
--- a/core/src/mindustry/game/Gamemode.java
+++ b/core/src/mindustry/game/Gamemode.java
@@ -23,7 +23,6 @@ public enum Gamemode{
rules.waveTimer = true;
rules.waveSpacing /= 2f;
- rules.teams.get(rules.waveTeam).ai = true;
rules.teams.get(rules.waveTeam).infiniteResources = true;
}, map -> map.teams.contains(state.rules.waveTeam.id)),
pvp(rules -> {
diff --git a/core/src/mindustry/game/Objectives.java b/core/src/mindustry/game/Objectives.java
index a4097c51fc..7fc844a180 100644
--- a/core/src/mindustry/game/Objectives.java
+++ b/core/src/mindustry/game/Objectives.java
@@ -28,7 +28,6 @@ public class Objectives{
}
}
- //TODO fix
public static class SectorComplete extends SectorObjective{
public SectorComplete(SectorPreset zone){
@@ -39,12 +38,12 @@ public class Objectives{
@Override
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
public String display(){
- return Core.bundle.format("requirement.wave", preset.sector.save == null ? "" : preset.sector.save.meta.rules.winWave, preset.localizedName);
+ return Core.bundle.format("requirement.capture", preset.localizedName);
}
}
diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java
index a101fe8592..2a927cc59b 100644
--- a/core/src/mindustry/game/Rules.java
+++ b/core/src/mindustry/game/Rules.java
@@ -82,6 +82,8 @@ public class Rules{
public Seq weather = new Seq<>(1);
/** Blocks that cannot be placed. */
public ObjectSet bannedBlocks = new ObjectSet<>();
+ /** Unlocked content names. Only used in multiplayer when the campaign is enabled. */
+ public ObjectSet researched = new ObjectSet<>();
/** Whether ambient lighting is enabled. */
public boolean lighting = false;
/** Whether enemy lighting is visible.
@@ -104,6 +106,8 @@ public class Rules{
public boolean ai;
/** TODO Tier of blocks/designs that the AI uses for building. [0, 1]*/
public float aiTier = 0f;
+ /** Whether, when AI is enabled, ships should be spawned from the core. */
+ public boolean aiCoreSpawn = true;
/** If true, blocks don't require power or resources. */
public boolean cheat;
/** If true, resources are not consumed when building. */
diff --git a/core/src/mindustry/game/Schematics.java b/core/src/mindustry/game/Schematics.java
index 596a571d3a..20fe7c36c4 100644
--- a/core/src/mindustry/game/Schematics.java
+++ b/core/src/mindustry/game/Schematics.java
@@ -16,6 +16,7 @@ import arc.util.pooling.*;
import arc.util.serialization.*;
import mindustry.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
@@ -608,8 +609,8 @@ public class Schematics implements Loadable{
wx = wy;
wy = -x;
}
- req.x = (short)(world.toTile(wx - req.block.offset) + ox);
- req.y = (short)(world.toTile(wy - req.block.offset) + oy);
+ req.x = (short)(World.toTile(wx - req.block.offset) + ox);
+ req.y = (short)(World.toTile(wy - req.block.offset) + oy);
req.rotation = (byte)Mathf.mod(req.rotation + direction, 4);
});
diff --git a/core/src/mindustry/game/SectorInfo.java b/core/src/mindustry/game/SectorInfo.java
index b66dc5bf95..3b2b4df3dd 100644
--- a/core/src/mindustry/game/SectorInfo.java
+++ b/core/src/mindustry/game/SectorInfo.java
@@ -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.*;
@@ -25,9 +26,9 @@ public class SectorInfo{
/** Export statistics. */
public ObjectMap- export = new ObjectMap<>();
/** Items stored in all cores. */
- public ItemSeq coreItems = new ItemSeq();
+ public ItemSeq items = new ItemSeq();
/** The best available core type. */
- public Block bestCoreType = Blocks.air;
+ public Block bestCoreType = Blocks.coreShard;
/** Max storage capacity. */
public int storageCapacity = 0;
/** Whether a core is available here. */
@@ -38,8 +39,27 @@ public class SectorInfo{
public @Nullable Sector destination;
/** Resources known to occur at this sector. */
public Seq resources = new Seq<>();
- /** Time spent at this sector. Do not use unless you know what you're doing. */
- public transient float internalTimeSpent;
+ /** Whether waves are enabled here. */
+ public boolean waves = true;
+ /** Whether attack mode is enabled here. */
+ public boolean attack = false;
+ /** 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. */
private transient Interval time = new Interval();
@@ -79,26 +99,68 @@ public class SectorInfo{
return export.get(item, ExportStat::new).mean;
}
+ /** Write contents of meta into main storage. */
+ public void write(){
+ //enable attack mode when there's a core.
+ if(state.rules.waveTeam.core() != null){
+ attack = true;
+ winWave = 0;
+ }
+
+ //if there are infinite waves and no win wave, add a win wave.
+ if(waves && winWave <= 0 && !attack){
+ winWave = 30;
+ }
+
+ state.wave = wave;
+ state.rules.waves = waves;
+ state.rules.waveSpacing = waveSpacing;
+ state.rules.winWave = winWave;
+ state.rules.attackMode = attack;
+
+ 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.storageCapacity)));
+ }
+ }
+
/** Prepare data for writing to a save. */
public void prepare(){
//update core items
- coreItems.clear();
+ items.clear();
CoreBuild entity = state.rules.defaultTeam.core();
if(entity != null){
ItemModule items = entity.items;
for(int i = 0; i < items.length(); i++){
- coreItems.set(content.item(i), items.get(i));
+ this.items.set(content.item(i), items.get(i));
}
+
+ spawnPosition = entity.pos();
}
+ waveSpacing = state.rules.waveSpacing;
+ wave = state.wave;
+ winWave = state.rules.winWave;
+ waves = state.rules.waves;
+ attack = state.rules.attackMode;
hasCore = entity != null;
bestCoreType = !hasCore ? Blocks.air : state.rules.defaultTeam.cores().max(e -> e.block.size).block;
storageCapacity = entity != null ? entity.storageCapacity : 0;
+ secondsPassed = 0;
+ wavesPassed = 0;
+ damage = 0;
- //update sector's internal time spent counter
- state.rules.sector.setTimeSpent(internalTimeSpent);
+ if(state.rules.sector != null){
+ state.rules.sector.info = this;
+ state.rules.sector.saveInfo();
+ }
+
+ SectorDamage.writeParameters(this);
}
/** Update averages of various stats, updates some special sector logic.
@@ -107,14 +169,6 @@ public class SectorInfo{
//updating in multiplayer as a client doesn't make sense
if(net.client()) return;
- internalTimeSpent += Time.delta;
-
- //autorun turns
- if(internalTimeSpent >= turnDuration){
- internalTimeSpent = 0;
- universe.runTurn();
- }
-
CoreBuild ent = state.rules.defaultTeam.core();
//refresh throughput
diff --git a/core/src/mindustry/game/SpawnGroup.java b/core/src/mindustry/game/SpawnGroup.java
index f9ae7c74d3..86ba7b6324 100644
--- a/core/src/mindustry/game/SpawnGroup.java
+++ b/core/src/mindustry/game/SpawnGroup.java
@@ -9,6 +9,8 @@ import mindustry.gen.*;
import mindustry.io.legacy.*;
import mindustry.type.*;
+import java.util.*;
+
import static mindustry.Vars.*;
/**
@@ -52,8 +54,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 +63,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 +83,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;
}
@@ -133,4 +140,20 @@ public class SpawnGroup implements Serializable{
", items=" + items +
'}';
}
+
+ @Override
+ public boolean equals(Object o){
+ if(this == o) return true;
+ if(o == null || getClass() != o.getClass()) return false;
+ SpawnGroup group = (SpawnGroup)o;
+ return end == group.end && begin == group.begin && spacing == group.spacing && max == group.max
+ && Float.compare(group.unitScaling, unitScaling) == 0 && Float.compare(group.shields, shields) == 0
+ && Float.compare(group.shieldScaling, shieldScaling) == 0 && unitAmount == group.unitAmount &&
+ type == group.type && effect == group.effect && Structs.eq(items, group.items);
+ }
+
+ @Override
+ public int hashCode(){
+ return Arrays.hashCode(new Object[]{type, end, begin, spacing, max, unitScaling, shields, shieldScaling, unitAmount, effect, items});
+ }
}
diff --git a/core/src/mindustry/game/Stats.java b/core/src/mindustry/game/Stats.java
index 1d175607ba..b963223518 100644
--- a/core/src/mindustry/game/Stats.java
+++ b/core/src/mindustry/game/Stats.java
@@ -40,7 +40,7 @@ public class Stats{
//weigh used fractions
float frac = 0f;
- Seq
- obtainable = zone.save == null ? new Seq<>() : zone.save.meta.secinfo.resources.select(i -> i instanceof Item).as();
+ Seq
- obtainable = zone.save == null ? new Seq<>() : zone.info.resources.select(i -> i instanceof Item).as();
for(Item item : obtainable){
frac += Mathf.clamp((float)itemsDelivered.get(item, 0) / capacity) / (float)obtainable.size;
}
diff --git a/core/src/mindustry/game/Team.java b/core/src/mindustry/game/Team.java
index 1234c7b2d7..cbed06f4e7 100644
--- a/core/src/mindustry/game/Team.java
+++ b/core/src/mindustry/game/Team.java
@@ -31,9 +31,9 @@ public class Team implements Comparable{
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);
diff --git a/core/src/mindustry/game/Teams.java b/core/src/mindustry/game/Teams.java
index aa390c5d26..6a2972f074 100644
--- a/core/src/mindustry/game/Teams.java
+++ b/core/src/mindustry/game/Teams.java
@@ -131,7 +131,7 @@ public class Teams{
}
private void count(Unit unit){
- unit.team.data().updateCount(unit.type(), 1);
+ unit.team.data().updateCount(unit.type, 1);
if(unit instanceof Payloadc){
((Payloadc)unit).payloads().each(p -> {
@@ -178,15 +178,15 @@ public class Teams{
data.units.add(unit);
data.presentFlag = true;
- if(data.unitsByType == null || data.unitsByType.length <= unit.type().id){
+ if(data.unitsByType == null || data.unitsByType.length <= unit.type.id){
data.unitsByType = new Seq[content.units().size];
}
- if(data.unitsByType[unit.type().id] == null){
- data.unitsByType[unit.type().id] = new Seq<>();
+ if(data.unitsByType[unit.type.id] == null){
+ data.unitsByType[unit.type.id] = new Seq<>();
}
- data.unitsByType[unit.type().id].add(unit);
+ data.unitsByType[unit.type.id].add(unit);
count(unit);
}
diff --git a/core/src/mindustry/game/Universe.java b/core/src/mindustry/game/Universe.java
index 892b93b731..30f0100b2d 100644
--- a/core/src/mindustry/game/Universe.java
+++ b/core/src/mindustry/game/Universe.java
@@ -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.*;
@@ -17,6 +18,7 @@ public class Universe{
private int netSeconds;
private float secondCounter;
private int turn;
+ private float turnCounter;
private Schematic lastLoadout;
private ItemSeq lastLaunchResources = new ItemSeq();
@@ -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 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. */
public void update(){
//only update time when not in multiplayer
if(!net.client()){
secondCounter += Time.delta / 60f;
+ turnCounter += Time.delta;
+
+ //auto-run turns
+ if(turnCounter >= turnDuration){
+ turnCounter = 0;
+ runTurn();
+ }
if(secondCounter >= 1){
seconds += (int)secondCounter;
@@ -132,42 +136,81 @@ public class Universe{
//update relevant sectors
for(Planet planet : content.planets()){
for(Sector sector : planet.sectors){
- if(sector.hasSave()){
- int spent = (int)(sector.getTimeSpent() / 60);
- int actuallyPassed = Math.max(newSecondsPassed - spent, 0);
+ if(sector.hasSave() && sector.hasBase()){
//increment seconds passed for this sector by the time that just passed with this turn
if(!sector.isBeingPlayed()){
- 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...
- /*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));
- //if so, just delete the save for now. it's lost.
- //TODO don't delete it later maybe
- sector.save.delete();
- //clear recieved
- sector.setExtraItems(new ItemSeq());
- sector.save = null;
- }*/
+ //sector is dead.
+ sector.info.items.clear();
+ sector.info.damage = 1f;
+ sector.info.hasCore = false;
+ sector.info.production.clear();
+ }else if(attacked && wavesPassed > 0 && sector.info.winWave > 1 && sector.info.wave + wavesPassed >= sector.info.winWave && !sector.hasEnemyBase()){
+ //autocapture the sector
+ sector.info.waves = false;
+
+ //fire the event
+ Events.fire(new SectorCaptureEvent(sector));
+ }
+
+ float scl = sector.getProductionScale();
+
+ //export to another sector
+ if(sector.info.destination != null){
+ Sector to = sector.info.destination;
+ if(to.hasBase()){
+ ItemSeq items = new ItemSeq();
+ //calculated exported items to this sector
+ sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * scl)));
+ to.addItems(items);
+ }
+ }
+
+ //add production, making sure that it's capped
+ sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * seconds * scl), sector.info.storageCapacity - sector.info.items.get(item))));
+
+ sector.saveInfo();
}
- //export to another sector
- if(sector.save != null && sector.save.meta != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.destination != null){
- Sector to = sector.save.meta.secinfo.destination;
- if(to.save != null){
- ItemSeq items = new ItemSeq();
- //calculated exported items to this sector
- sector.save.meta.secinfo.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed)));
- to.addItems(items);
+ //queue random invasions
+ if(!sector.isAttacked() && turn > invasionGracePeriod){
+ //invasion chance depends on # of nearby bases
+ if(Mathf.chance(baseInvasionChance * sector.near().count(Sector::hasEnemyBase))){
+ int waveMax = Math.max(sector.info.winWave, sector.isBeingPlayed() ? state.wave : 0) + Mathf.random(2, 5) * 5;
+
+ //assign invasion-related things
+ if(sector.isBeingPlayed()){
+ state.rules.winWave = waveMax;
+ state.rules.waves = true;
+ }else{
+ sector.info.winWave = waveMax;
+ sector.info.waves = true;
+ sector.saveInfo();
+ }
+
+ Events.fire(new SectorInvasionEvent(sector));
}
}
-
- //reset time spent to 0
- sector.setTimeSpent(0f);
}
}
}
@@ -184,7 +227,7 @@ public class Universe{
for(Planet planet : content.planets()){
for(Sector sector : planet.sectors){
if(sector.hasSave()){
- count.add(sector.calculateItems());
+ count.add(sector.items());
}
}
}
diff --git a/core/src/mindustry/graphics/BlockRenderer.java b/core/src/mindustry/graphics/BlockRenderer.java
index a36230f0aa..afe2e3182b 100644
--- a/core/src/mindustry/graphics/BlockRenderer.java
+++ b/core/src/mindustry/graphics/BlockRenderer.java
@@ -20,11 +20,14 @@ import static arc.Core.*;
import static mindustry.Vars.*;
public class BlockRenderer implements Disposable{
+ public static final int crackRegions = 8, maxCrackSize = 9;
+
private static final int initialRequests = 32 * 32;
private static final int expandr = 9;
private static final Color shadowColor = new Color(0, 0, 0, 0.71f);
public final FloorRenderer floor = new FloorRenderer();
+ public TextureRegion[][] cracks;
private Seq tileview = new Seq<>(false, initialRequests, Tile.class);
private Seq lightview = new Seq<>(false, initialRequests, Tile.class);
@@ -35,11 +38,20 @@ public class BlockRenderer implements Disposable{
private FrameBuffer dark = new FrameBuffer();
private Seq outArray2 = new Seq<>();
private Seq shadowEvents = new Seq<>();
- private IntSet processedEntities = new IntSet(), processedLinks = new IntSet();
+ private IntSet procEntities = new IntSet(), procLinks = new IntSet(), procLights = new IntSet();
private boolean displayStatus = false;
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 -> {
shadowEvents.clear();
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
@@ -179,8 +191,9 @@ public class BlockRenderer implements Disposable{
tileview.clear();
lightview.clear();
- processedEntities.clear();
- processedLinks.clear();
+ procEntities.clear();
+ procLinks.clear();
+ procLights.clear();
int minx = Math.max(avgx - rangex - expandr, 0);
int miny = Math.max(avgy - rangey - expandr, 0);
@@ -197,25 +210,25 @@ public class BlockRenderer implements Disposable{
tile = tile.build.tile;
}
- if(block != Blocks.air && block.cacheLayer == CacheLayer.normal && (tile.build == null || !processedEntities.contains(tile.build.id))){
+ if(block != Blocks.air && block.cacheLayer == CacheLayer.normal && (tile.build == null || !procEntities.contains(tile.build.id))){
if(block.expanded || !expanded){
- if(tile.build == null || processedLinks.add(tile.build.id)){
+ if(tile.build == null || procLinks.add(tile.build.id)){
tileview.add(tile);
if(tile.build != null){
- processedEntities.add(tile.build.id);
- processedLinks.add(tile.build.id);
+ procEntities.add(tile.build.id);
+ procLinks.add(tile.build.id);
}
}
}
//lights are drawn even in the expanded range
- if(tile.build != null || tile.block().emitLight){
+ if(((tile.build != null && procLights.add(tile.build.pos())) || tile.block().emitLight)){
lightview.add(tile);
}
if(tile.build != null && tile.build.power != null && tile.build.power.links.size > 0){
for(Building other : tile.build.getPowerConnections(outArray2)){
- if(other.block instanceof PowerNode && processedLinks.add(other.id)){ //TODO need a generic way to render connections!
+ if(other.block instanceof PowerNode && procLinks.add(other.id)){ //TODO need a generic way to render connections!
tileview.add(other.tile);
}
}
@@ -223,7 +236,7 @@ public class BlockRenderer implements Disposable{
}
//special case for floors
- if(block == Blocks.air && tile.floor().emitLight){
+ if((block == Blocks.air && tile.floor().emitLight) && procLights.add(tile.pos())){
lightview.add(tile);
}
}
diff --git a/core/src/mindustry/graphics/LightRenderer.java b/core/src/mindustry/graphics/LightRenderer.java
index 10ffa727b6..0a05c7409b 100644
--- a/core/src/mindustry/graphics/LightRenderer.java
+++ b/core/src/mindustry/graphics/LightRenderer.java
@@ -100,7 +100,6 @@ public class LightRenderer{
Draw.vert(ledge.texture, vertices, 0, vertices.length);
-
Vec2 v3 = Tmp.v2.trnsExact(rot, stroke);
u = ledge.u;
diff --git a/core/src/mindustry/graphics/MinimapRenderer.java b/core/src/mindustry/graphics/MinimapRenderer.java
index cdb84e9044..1c34b4424e 100644
--- a/core/src/mindustry/graphics/MinimapRenderer.java
+++ b/core/src/mindustry/graphics/MinimapRenderer.java
@@ -96,7 +96,7 @@ public class MinimapRenderer implements Disposable{
Draw.mixcol(unit.team().color, 1f);
float scale = Scl.scl(1f) / 2f * scaling * 32f;
- Draw.rect(unit.type().icon(Cicon.full), x + rx, y + ry, scale, scale, unit.rotation() - 90);
+ Draw.rect(unit.type.icon(Cicon.full), x + rx, y + ry, scale, scale, unit.rotation() - 90);
Draw.reset();
//only disable player names in multiplayer
diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java
index 19569ed1a3..9bb3a95fa3 100644
--- a/core/src/mindustry/graphics/OverlayRenderer.java
+++ b/core/src/mindustry/graphics/OverlayRenderer.java
@@ -85,7 +85,7 @@ public class OverlayRenderer{
//special selection for block "units"
Fill.square(select.x, select.y, ((BlockUnitc)select).tile().block.size * tilesize/2f);
}else{
- Draw.rect(select.type().icon(Cicon.full), select.x(), select.y(), select.rotation() - 90);
+ Draw.rect(select.type.icon(Cicon.full), select.x(), select.y(), select.rotation() - 90);
}
Lines.stroke(unitFade);
@@ -121,10 +121,12 @@ public class OverlayRenderer{
Lines.stroke(2f);
Draw.color(Color.gray, Color.lightGray, Mathf.absin(Time.time(), 8f, 1f));
- for(Tile tile : spawner.getSpawns()){
- if(tile.within(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){
- Draw.alpha(Mathf.clamp(1f - (player.dst(tile) - state.rules.dropZoneRadius) / spawnerMargin));
- Lines.dashCircle(tile.worldx(), tile.worldy(), state.rules.dropZoneRadius);
+ if(state.hasSpawns()){
+ for(Tile tile : spawner.getSpawns()){
+ if(tile.within(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){
+ Draw.alpha(Mathf.clamp(1f - (player.dst(tile) - state.rules.dropZoneRadius) / spawnerMargin));
+ Lines.dashCircle(tile.worldx(), tile.worldy(), state.rules.dropZoneRadius);
+ }
}
}
diff --git a/core/src/mindustry/graphics/g3d/PlanetGrid.java b/core/src/mindustry/graphics/g3d/PlanetGrid.java
index e4972e3eb7..380f937658 100644
--- a/core/src/mindustry/graphics/g3d/PlanetGrid.java
+++ b/core/src/mindustry/graphics/g3d/PlanetGrid.java
@@ -22,15 +22,16 @@ public class PlanetGrid{
{5, 3, 10, 1, 4}, {2, 5, 4, 0, 11}, {3, 7, 6, 1, 8}, {7, 2, 9, 0, 6}
};
- public final int size;
- public final Ptile[] tiles;
- public final Corner[] corners;
- public final Edge[] edges;
+ public int size;
+ public Ptile[] tiles;
+ public Corner[] corners;
+ public Edge[] edges;
- PlanetGrid(int size){
+ //this is protected so if you want to make strange grids you should know what you're doing.
+ protected PlanetGrid(int size){
this.size = size;
- tiles = new Ptile[Buildingount(size)];
+ tiles = new Ptile[tileCount(size)];
for(int i = 0; i < tiles.length; i++){
tiles[i] = new Ptile(i, i < 12 ? 5 : 6);
}
@@ -67,7 +68,7 @@ public class PlanetGrid{
return result;
}
- static PlanetGrid initialGrid(){
+ public static PlanetGrid initialGrid(){
PlanetGrid grid = new PlanetGrid(0);
for(Ptile t : grid.tiles){
@@ -111,7 +112,7 @@ public class PlanetGrid{
return grid;
}
- static PlanetGrid subdividedGrid(PlanetGrid prev){
+ public static PlanetGrid subdividedGrid(PlanetGrid prev){
PlanetGrid grid = new PlanetGrid(prev.size + 1);
int prevTiles = prev.tiles.length;
@@ -207,7 +208,7 @@ public class PlanetGrid{
return -1;
}
- static int Buildingount(int size){
+ static int tileCount(int size){
return 10 * Mathf.pow(3, size) + 2;
}
@@ -220,12 +221,12 @@ public class PlanetGrid{
}
public static class Ptile{
- public final int id;
- public final int edgeCount;
+ public int id;
+ public int edgeCount;
- public final Ptile[] tiles;
- public final Corner[] corners;
- public final Edge[] edges;
+ public Ptile[] tiles;
+ public Corner[] corners;
+ public Edge[] edges;
public Vec3 v = new Vec3();
@@ -240,11 +241,11 @@ public class PlanetGrid{
}
public static class Corner{
- public final int id;
- public final Ptile[] tiles = new Ptile[3];
- public final Corner[] corners = new Corner[3];
- public final Edge[] edges = new Edge[3];
- public final Vec3 v = new Vec3();
+ public int id;
+ public Ptile[] tiles = new Ptile[3];
+ public Corner[] corners = new Corner[3];
+ public Edge[] edges = new Edge[3];
+ public Vec3 v = new Vec3();
public Corner(int id){
this.id = id;
@@ -252,9 +253,9 @@ public class PlanetGrid{
}
public static class Edge{
- public final int id;
- public final Ptile[] tiles = new Ptile[2];
- public final Corner[] corners = new Corner[2];
+ public int id;
+ public Ptile[] tiles = new Ptile[2];
+ public Corner[] corners = new Corner[2];
public Edge(int id){
this.id = id;
diff --git a/core/src/mindustry/graphics/g3d/PlanetRenderer.java b/core/src/mindustry/graphics/g3d/PlanetRenderer.java
index e3eb74a7b5..405d668333 100644
--- a/core/src/mindustry/graphics/g3d/PlanetRenderer.java
+++ b/core/src/mindustry/graphics/g3d/PlanetRenderer.java
@@ -10,6 +10,7 @@ import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
+import mindustry.game.EventType.*;
import mindustry.graphics.*;
import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.type.*;
@@ -38,19 +39,19 @@ public class PlanetRenderer implements Disposable{
public float zoom = 1f;
private final Mesh[] outlines = new Mesh[10];
- private final PlaneBatch3D projector = new PlaneBatch3D();
- private final Mat3D mat = new Mat3D();
- private final FrameBuffer buffer = new FrameBuffer(2, 2, true);
- private PlanetInterfaceRenderer irenderer;
+ public final PlaneBatch3D projector = new PlaneBatch3D();
+ public final Mat3D mat = new Mat3D();
+ public final FrameBuffer buffer = new FrameBuffer(2, 2, true);
+ public PlanetInterfaceRenderer irenderer;
- private final Bloom bloom = new Bloom(Core.graphics.getWidth()/4, Core.graphics.getHeight()/4, true, false){{
+ public final Bloom bloom = new Bloom(Core.graphics.getWidth()/4, Core.graphics.getHeight()/4, true, false){{
setThreshold(0.8f);
blurPasses = 6;
}};
- private final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, false, 1.5f);
+ public final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, false, 1.5f);
//seed: 8kmfuix03fw
- private final CubemapMesh skybox = new CubemapMesh(new Cubemap("cubemaps/stars/"));
+ public final CubemapMesh skybox = new CubemapMesh(new Cubemap("cubemaps/stars/"));
public PlanetRenderer(){
camPos.set(0, 0f, camLength);
@@ -82,14 +83,20 @@ public class PlanetRenderer implements Disposable{
projector.proj(cam.combined);
batch.proj(cam.combined);
+ Events.fire(Trigger.universeDrawBegin);
+
beginBloom();
skybox.render(cam.combined);
+ Events.fire(Trigger.universeDraw);
+
renderPlanet(solarSystem);
endBloom();
+ Events.fire(Trigger.universeDrawEnd);
+
Gl.enable(Gl.blend);
irenderer.renderProjections();
@@ -100,18 +107,21 @@ public class PlanetRenderer implements Disposable{
cam.update();
}
- private void beginBloom(){
+ public void beginBloom(){
bloom.resize(Core.graphics.getWidth() / 4, Core.graphics.getHeight() / 4);
bloom.capture();
}
- private void endBloom(){
+ public void endBloom(){
bloom.render();
}
- private void renderPlanet(Planet planet){
+
+ public void renderPlanet(Planet planet){
+ if(!planet.visible()) return;
+
//render planet at offsetted position in the world
- planet.mesh.render(cam.combined, planet.getTransform(mat));
+ planet.draw(cam.combined, planet.getTransform(mat));
renderOrbit(planet);
@@ -137,8 +147,8 @@ public class PlanetRenderer implements Disposable{
}
}
- private void renderOrbit(Planet planet){
- if(planet.parent == null) return;
+ public void renderOrbit(Planet planet){
+ if(planet.parent == null || !planet.visible()) return;
Vec3 center = planet.parent.position;
float radius = planet.orbitRadius;
@@ -147,7 +157,7 @@ public class PlanetRenderer implements Disposable{
batch.flush(Gl.lineLoop);
}
- private void renderSectors(Planet planet){
+ public void renderSectors(Planet planet){
//apply transformed position
batch.proj().mul(planet.getTransform(mat));
@@ -268,7 +278,7 @@ public class PlanetRenderer implements Disposable{
}
}
- private Mesh outline(int size){
+ public Mesh outline(int size){
if(outlines[size] == null){
outlines[size] = MeshBuilder.buildHex(new HexMesher(){
@Override
diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java
index 7099f254dd..f305d661fc 100644
--- a/core/src/mindustry/input/DesktopInput.java
+++ b/core/src/mindustry/input/DesktopInput.java
@@ -12,6 +12,7 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
+import mindustry.core.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -351,10 +352,6 @@ public class DesktopInput extends InputHandler{
table.button(Icon.map, Styles.clearPartiali, () -> {
ui.planet.show();
}).visible(() -> state.isCampaign()).tooltip("@planetmap");
-
- table.button(Icon.up, Styles.clearPartiali, () -> {
- ui.planet.showLaunch(state.getSector(), player.team().core());
- }).visible(() -> state.isCampaign()).tooltip("@launchcore").disabled(b -> player.team().core() == null);
}
void pollInput(){
@@ -363,7 +360,7 @@ public class DesktopInput extends InputHandler{
Tile selected = tileAt(Core.input.mouseX(), Core.input.mouseY());
int cursorX = tileX(Core.input.mouseX());
int cursorY = tileY(Core.input.mouseY());
- int rawCursorX = world.toTile(Core.input.mouseWorld().x), rawCursorY = world.toTile(Core.input.mouseWorld().y);
+ int rawCursorX = World.toTile(Core.input.mouseWorld().x), rawCursorY = World.toTile(Core.input.mouseWorld().y);
// automatically pause building if the current build queue is empty
if(Core.settings.getBool("buildautopause") && isBuilding && !player.builder().isBuilding()){
@@ -599,11 +596,11 @@ public class DesktopInput extends InputHandler{
}
protected void updateMovement(Unit unit){
- boolean omni = unit.type().omniMovement;
+ boolean omni = unit.type.omniMovement;
boolean ground = unit.isGrounded();
- float strafePenalty = ground ? 1f : Mathf.lerp(1f, unit.type().strafePenalty, Angles.angleDist(unit.vel().angle(), unit.rotation()) / 180f);
- float baseSpeed = unit.type().speed;
+ float strafePenalty = ground ? 1f : Mathf.lerp(1f, unit.type.strafePenalty, Angles.angleDist(unit.vel().angle(), unit.rotation()) / 180f);
+ float baseSpeed = unit.type.speed;
//limit speed to minimum formation speed to preserve formation
if(unit.isCommanding()){
@@ -611,7 +608,7 @@ public class DesktopInput extends InputHandler{
baseSpeed = unit.minFormationSpeed * 0.95f;
}
- float speed = baseSpeed * Mathf.lerp(1f, unit.isCommanding() ? 1f : unit.type().canBoost ? unit.type().boostMultiplier : 1f, unit.elevation) * strafePenalty;
+ float speed = baseSpeed * Mathf.lerp(1f, unit.isCommanding() ? 1f : unit.type.canBoost ? unit.type.boostMultiplier : 1f, unit.elevation) * strafePenalty;
float xa = Core.input.axis(Binding.move_x);
float ya = Core.input.axis(Binding.move_y);
boolean boosted = (unit instanceof Mechc && unit.isFlying());
@@ -622,7 +619,7 @@ public class DesktopInput extends InputHandler{
}
float mouseAngle = Angles.mouseAngle(unit.x, unit.y);
- boolean aimCursor = omni && player.shooting && unit.type().hasWeapons() && unit.type().faceTarget && !boosted && unit.type().rotateShooting;
+ boolean aimCursor = omni && player.shooting && unit.type.hasWeapons() && unit.type.faceTarget && !boosted && unit.type.rotateShooting;
if(aimCursor){
unit.lookAt(mouseAngle);
@@ -637,11 +634,11 @@ public class DesktopInput extends InputHandler{
}else{
unit.moveAt(Tmp.v2.trns(unit.rotation, movement.len()));
if(!movement.isZero() && ground){
- unit.vel.rotateTo(movement.angle(), unit.type().rotateSpeed);
+ unit.vel.rotateTo(movement.angle(), unit.type.rotateSpeed);
}
}
- unit.aim(unit.type().faceTarget ? Core.input.mouseWorld() : Tmp.v1.trns(unit.rotation, Core.input.mouseWorld().dst(unit)).add(unit.x, unit.y));
+ unit.aim(unit.type.faceTarget ? Core.input.mouseWorld() : Tmp.v1.trns(unit.rotation, Core.input.mouseWorld().dst(unit)).add(unit.x, unit.y));
unit.controlWeapons(true, player.shooting && !boosted);
player.boosting = Core.input.keyDown(Binding.boost) && !movement.isZero();
@@ -660,7 +657,7 @@ public class DesktopInput extends InputHandler{
}
}
- //update commander inut
+ //update commander unit
if(Core.input.keyTap(Binding.command)){
Call.unitCommand(player);
}
diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java
index eebe40e15b..99103da3d7 100644
--- a/core/src/mindustry/input/InputHandler.java
+++ b/core/src/mindustry/input/InputHandler.java
@@ -16,6 +16,7 @@ import arc.util.*;
import mindustry.ai.formations.patterns.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
@@ -158,7 +159,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Payloadc pay = (Payloadc)unit;
if(target.isAI() && target.isGrounded() && pay.canPickup(target)
- && target.within(unit, unit.type().hitSize * 2f + target.type().hitSize * 2f)){
+ && target.within(unit, unit.type.hitSize * 2f + target.type.hitSize * 2f)){
Call.pickedUnitPayload(unit, target);
}
}
@@ -240,6 +241,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
pay.set(x, y);
pay.dropLastPayload();
pay.set(prevx, prevy);
+ pay.controlling().each(u -> {
+ if(u instanceof Payloadc){
+ Call.payloadDropped(u, u.x, u.y);
+ }
+ });
}
}
@@ -365,7 +371,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(commander.isCommanding()){
commander.clearCommand();
- }else if(player.unit().type().commandLimit > 0){
+ }else if(player.unit().type.commandLimit > 0){
//TODO try out some other formations
commander.commandNearby(new CircleFormation());
@@ -398,17 +404,17 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && player.unit().ammo <= 0){
- player.unit().type().weapons.first().noAmmoSound.at(player.unit());
+ player.unit().type.weapons.first().noAmmoSound.at(player.unit());
}
wasShooting = player.shooting;
if(!player.dead()){
- controlledType = player.unit().type();
+ controlledType = player.unit().type;
}
if(controlledType != null && player.dead()){
- Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.dead);
+ Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type == controlledType && !u.dead);
if(unit != null){
Call.unitControl(player, unit);
@@ -418,9 +424,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public void checkUnit(){
if(controlledType != null){
- Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.dead);
+ Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type == controlledType && !u.dead);
if(unit == null && controlledType == UnitTypes.block){
- unit = world.buildWorld(player.x, player.y) instanceof ControlBlock ? ((ControlBlock)world.buildWorld(player.x, player.y)).unit() : null;
+ unit = world.buildWorld(player.x, player.y) instanceof ControlBlock cont && cont.canControl() ? cont.unit() : null;
}
if(unit != null){
@@ -437,7 +443,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Unit unit = player.unit();
if(!(unit instanceof Payloadc pay)) return;
- Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitSize * 2.5f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
+ Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type.hitSize * 2.5f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
if(target != null){
Call.requestUnitPayload(player, target);
}else{
@@ -568,8 +574,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
wx = wy;
wy = -x;
}
- req.x = world.toTile(wx - req.block.offset) + ox;
- req.y = world.toTile(wy - req.block.offset) + oy;
+ req.x = World.toTile(wx - req.block.offset) + ox;
+ req.y = World.toTile(wy - req.block.offset) + oy;
req.rotation = Mathf.mod(req.rotation + direction, 4);
});
}
@@ -934,11 +940,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
int rawTileX(){
- return world.toTile(Core.input.mouseWorld().x);
+ return World.toTile(Core.input.mouseWorld().x);
}
int rawTileY(){
- return world.toTile(Core.input.mouseWorld().y);
+ return World.toTile(Core.input.mouseWorld().y);
}
int tileX(float cursorX){
@@ -946,7 +952,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(selectedBlock()){
vec.sub(block.offset, block.offset);
}
- return world.toTile(vec.x);
+ return World.toTile(vec.x);
}
int tileY(float cursorY){
@@ -954,7 +960,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(selectedBlock()){
vec.sub(block.offset, block.offset);
}
- return world.toTile(vec.y);
+ return World.toTile(vec.y);
}
public boolean selectedBlock(){
@@ -984,8 +990,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
Building tile = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
- if(tile instanceof ControlBlock && tile.team == player.team()){
- return ((ControlBlock)tile).unit();
+ if(tile instanceof ControlBlock cont && cont.canControl() && tile.team == player.team()){
+ return cont.unit();
}
return null;
diff --git a/core/src/mindustry/input/MobileInput.java b/core/src/mindustry/input/MobileInput.java
index 19f458ea88..981f55bd6f 100644
--- a/core/src/mindustry/input/MobileInput.java
+++ b/core/src/mindustry/input/MobileInput.java
@@ -14,6 +14,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
@@ -23,6 +24,7 @@ import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
+import mindustry.world.blocks.*;
import static mindustry.Vars.*;
import static mindustry.input.PlaceMode.*;
@@ -85,7 +87,7 @@ public class MobileInput extends InputHandler implements GestureListener{
if(tile != null && player.team().isEnemy(tile.team)){
player.miner().mineTile(null);
target = tile;
- }else if(tile != null && player.unit().type().canHeal && tile.team == player.team() && tile.damaged()){
+ }else if(tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged()){
player.miner().mineTile(null);
target = tile;
}
@@ -405,14 +407,14 @@ public class MobileInput extends InputHandler implements GestureListener{
protected int schemOriginX(){
Tmp.v1.setZero();
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
- return world.toTile(Tmp.v1.scl(1f / selectRequests.size).x);
+ return World.toTile(Tmp.v1.scl(1f / selectRequests.size).x);
}
@Override
protected int schemOriginY(){
Tmp.v1.setZero();
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
- return world.toTile(Tmp.v1.scl(1f / selectRequests.size).y);
+ return World.toTile(Tmp.v1.scl(1f / selectRequests.size).y);
}
@Override
@@ -834,10 +836,10 @@ public class MobileInput extends InputHandler implements GestureListener{
protected void updateMovement(Unit unit){
Rect rect = Tmp.r3;
- UnitType type = unit.type();
+ UnitType type = unit.type;
if(type == null) return;
- boolean omni = unit.type().omniMovement;
+ boolean omni = unit.type.omniMovement;
boolean legs = unit.isGrounded();
boolean allowHealing = type.canHeal;
boolean validHealTarget = allowHealing && target instanceof Building && ((Building)target).isValid() && target.team() == unit.team &&
@@ -855,7 +857,7 @@ public class MobileInput extends InputHandler implements GestureListener{
float attractDst = 15f;
float strafePenalty = legs ? 1f : Mathf.lerp(1f, type.strafePenalty, Angles.angleDist(unit.vel.angle(), unit.rotation) / 180f);
- float baseSpeed = unit.type().speed;
+ float baseSpeed = unit.type.speed;
//limit speed to minimum formation speed to preserve formation
if(unit.isCommanding()){
@@ -935,7 +937,7 @@ public class MobileInput extends InputHandler implements GestureListener{
unit.aim(player.mouseX = Core.input.mouseWorldX(), player.mouseY = Core.input.mouseWorldY());
}else if(target == null){
player.shooting = false;
- if(Core.settings.getBool("autotarget")){
+ if(Core.settings.getBool("autotarget") && !(player.unit() instanceof BlockUnitUnit u && u.tile() instanceof ControlBlock c && !c.shouldAutoTarget())){
target = Units.closestTarget(unit.team, unit.x, unit.y, range, u -> u.team != Team.derelict, u -> u.team != Team.derelict);
if(allowHealing && target == null){
diff --git a/core/src/mindustry/io/JsonIO.java b/core/src/mindustry/io/JsonIO.java
index 39c3a1a821..eb3815ba76 100644
--- a/core/src/mindustry/io/JsonIO.java
+++ b/core/src/mindustry/io/JsonIO.java
@@ -165,6 +165,18 @@ public class JsonIO{
}
});
+ json.setSerializer(UnitType.class, new Serializer<>(){
+ @Override
+ public void write(Json json, UnitType object, Class knownType){
+ json.writeValue(object.name);
+ }
+
+ @Override
+ public UnitType read(Json json, JsonValue jsonData, Class type){
+ return Vars.content.getByName(ContentType.unit, jsonData.asString());
+ }
+ });
+
json.setSerializer(ItemStack.class, new Serializer<>(){
@Override
public void write(Json json, ItemStack object, Class knownType){
diff --git a/core/src/mindustry/io/SaveMeta.java b/core/src/mindustry/io/SaveMeta.java
index f2e039f634..c6133889bc 100644
--- a/core/src/mindustry/io/SaveMeta.java
+++ b/core/src/mindustry/io/SaveMeta.java
@@ -14,12 +14,10 @@ public class SaveMeta{
public Map map;
public int wave;
public Rules rules;
- public SectorInfo secinfo;
public StringMap tags;
public String[] mods;
- public boolean hasProduction;
- public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, SectorInfo secinfo, StringMap tags){
+ public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, StringMap tags){
this.version = version;
this.build = build;
this.timestamp = timestamp;
@@ -29,8 +27,5 @@ public class SaveMeta{
this.rules = rules;
this.tags = tags;
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
- this.secinfo = secinfo;
-
- secinfo.production.each((e, amount) -> hasProduction |= amount.mean > 0.001f);
}
}
diff --git a/core/src/mindustry/io/SaveVersion.java b/core/src/mindustry/io/SaveVersion.java
index cacac7700a..e153b74587 100644
--- a/core/src/mindustry/io/SaveVersion.java
+++ b/core/src/mindustry/io/SaveVersion.java
@@ -40,7 +40,6 @@ public abstract class SaveVersion extends SaveFileReader{
map.get("mapname"),
map.getInt("wave"),
JsonIO.read(Rules.class, map.get("rules", "{}")),
- JsonIO.read(SectorInfo.class, map.get("secinfo", "{}")),
map
);
}
@@ -74,6 +73,7 @@ public abstract class SaveVersion extends SaveFileReader{
//prepare campaign data for writing
if(state.isCampaign()){
state.secinfo.prepare();
+ state.rules.sector.saveInfo();
}
//flush tech node progress
@@ -89,7 +89,6 @@ public abstract class SaveVersion extends SaveFileReader{
"wave", state.wave,
"wavetime", state.wavetime,
"stats", JsonIO.write(state.stats),
- "secinfo", state.isCampaign() ? JsonIO.write(state.secinfo) : "{}",
"rules", JsonIO.write(state.rules),
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
"width", world.width(),
@@ -107,14 +106,13 @@ public abstract class SaveVersion extends SaveFileReader{
state.wave = map.getInt("wave");
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
- state.secinfo = JsonIO.read(SectorInfo.class, map.get("secinfo", "{}"));
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
lastReadBuild = map.getInt("build", -1);
- //load time spent on sector into state
+ //load in sector info
if(state.rules.sector != null){
- state.secinfo.internalTimeSpent = state.rules.sector.getStoredTimeSpent();
+ state.secinfo = state.rules.sector.info;
}
if(!headless){
diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java
index 669cd65d53..2c2c19dcfc 100644
--- a/core/src/mindustry/io/TypeIO.java
+++ b/core/src/mindustry/io/TypeIO.java
@@ -185,7 +185,7 @@ public class TypeIO{
return unit == null ? Nulls.unit : unit;
}else if(type == 1){ //block
Building tile = world.build(id);
- return tile instanceof ControlBlock ? ((ControlBlock)tile).unit() : Nulls.unit;
+ return tile instanceof ControlBlock cont ? cont.unit() : Nulls.unit;
}
return Nulls.unit;
}
@@ -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);
}
diff --git a/core/src/mindustry/logic/LAccess.java b/core/src/mindustry/logic/LAccess.java
index 5dc881a778..0d678554d1 100644
--- a/core/src/mindustry/logic/LAccess.java
+++ b/core/src/mindustry/logic/LAccess.java
@@ -15,6 +15,8 @@ public enum LAccess{
powerNetCapacity,
powerNetIn,
powerNetOut,
+ ammo,
+ ammoCapacity,
health,
maxHealth,
heat,
diff --git a/core/src/mindustry/logic/LAssembler.java b/core/src/mindustry/logic/LAssembler.java
index 4f72d121aa..57c2004320 100644
--- a/core/src/mindustry/logic/LAssembler.java
+++ b/core/src/mindustry/logic/LAssembler.java
@@ -189,16 +189,28 @@ public class LAssembler{
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
}
+ //remove spaces for non-strings
+ symbol = symbol.replace(' ', '_');
+
try{
- double value = Double.parseDouble(symbol);
+ double value = 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;
+ return putConst("___" + value, value).id;
}catch(NumberFormatException e){
return putVar(symbol).id;
}
}
+ double parseDouble(String symbol) throws NumberFormatException{
+ //parse hex/binary syntax
+ if(symbol.startsWith("0b")) return Long.parseLong(symbol.substring(2), 2);
+ if(symbol.startsWith("0x")) return Long.parseLong(symbol.substring(2), 16);
+
+ return Double.parseDouble(symbol);
+ }
+
/** Adds a constant value by name. */
public BVar putConst(String name, Object value){
BVar var = putVar(name);
diff --git a/core/src/mindustry/logic/LCanvas.java b/core/src/mindustry/logic/LCanvas.java
index ed98bee8ff..e16aab6621 100644
--- a/core/src/mindustry/logic/LCanvas.java
+++ b/core/src/mindustry/logic/LCanvas.java
@@ -305,7 +305,7 @@ public class LCanvas extends Table{
statements.finishLayout();
}
});
- }).growX();
+ }).growX().height(38);
row();
diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java
index a8c5de8a9d..baf9566ca3 100644
--- a/core/src/mindustry/logic/LExecutor.java
+++ b/core/src/mindustry/logic/LExecutor.java
@@ -7,6 +7,7 @@ import arc.util.noise.*;
import mindustry.*;
import mindustry.ai.types.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.*;
@@ -110,12 +111,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 +130,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 : value;
v.objval = null;
v.isobj = false;
}
@@ -263,8 +264,8 @@ public class LExecutor{
if(res != null && (!build || res.build != null)){
cache.found = true;
//set result if found
- exec.setnum(outX, cache.x = build ? res.build.x : res.worldx());
- exec.setnum(outY, cache.y = build ? res.build.y : res.worldy());
+ exec.setnum(outX, cache.x = World.conv(build ? res.build.x : res.worldx()));
+ exec.setnum(outY, cache.y = World.conv(build ? res.build.y : res.worldy()));
exec.setnum(outFound, 1);
}else{
cache.found = false;
@@ -332,14 +333,15 @@ public class LExecutor{
//only control standard AI units
if(unitObj instanceof Unit unit && ai != null){
ai.controlTimer = LogicAI.logicControlTimeout;
+ float x1 = World.unconv(exec.numf(p1)), y1 = World.unconv(exec.numf(p2)), d1 = World.unconv(exec.numf(p3));
switch(type){
case move, stop, approach -> {
ai.control = type;
- ai.moveX = exec.numf(p1);
- ai.moveY = exec.numf(p2);
+ ai.moveX = x1;
+ ai.moveY = y1;
if(type == LUnitControl.approach){
- ai.moveRad = exec.numf(p3);
+ ai.moveRad = d1;
}
//stop mining/building
@@ -353,13 +355,13 @@ public class LExecutor{
}
}
case within -> {
- exec.setnum(p4, unit.within(exec.numf(p1), exec.numf(p2), exec.numf(p3)) ? 1 : 0);
+ exec.setnum(p4, unit.within(x1, y1, d1) ? 1 : 0);
}
case pathfind -> {
ai.control = type;
}
case target -> {
- ai.posTarget.set(exec.numf(p1), exec.numf(p2));
+ ai.posTarget.set(x1, y1);
ai.aimControl = type;
ai.mainTarget = null;
ai.shoot = exec.bool(p3);
@@ -376,7 +378,7 @@ public class LExecutor{
unit.flag = exec.num(p1);
}
case mine -> {
- Tile tile = world.tileWorld(exec.numf(p1), exec.numf(p2));
+ Tile tile = world.tileWorld(x1, y1);
if(unit instanceof Minerc miner){
miner.mineTile(miner.validMine(tile) ? tile : null);
}
@@ -395,7 +397,7 @@ public class LExecutor{
if(unit instanceof Payloadc pay){
//units
if(exec.bool(p1)){
- Unit result = Units.closest(unit.team, unit.x, unit.y, unit.type().hitSize * 2f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
+ Unit result = Units.closest(unit.team, unit.x, unit.y, unit.type.hitSize * 2f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
if(result != null){
Call.pickedUnitPayload(unit, result);
@@ -420,7 +422,7 @@ public class LExecutor{
}
case build -> {
if(unit instanceof Builderc builder && exec.obj(p3) instanceof Block block){
- int x = world.toTile(exec.numf(p1)), y = world.toTile(exec.numf(p2));
+ int x = World.toTile(x1), y = World.toTile(y1);
int rot = exec.numi(p4);
//reset state of last request when necessary
@@ -441,13 +443,12 @@ public class LExecutor{
}
}
case getBlock -> {
- float x = exec.numf(p1), y = exec.numf(p2);
float range = Math.max(unit.range(), buildingRange);
- if(!unit.within(x, y, range)){
+ if(!unit.within(x1, y1, range)){
exec.setobj(p3, null);
exec.setnum(p4, 0);
}else{
- Tile tile = world.tileWorld(x, y);
+ Tile tile = world.tileWorld(x1, y1);
//any environmental solid block is returned as StoneWall, aka "@solid"
Block block = tile == null ? null : !tile.synthetic() ? (tile.solid() ? Blocks.stoneWall : Blocks.air) : tile.block();
exec.setobj(p3, block);
@@ -737,7 +738,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;
}
}
diff --git a/core/src/mindustry/maps/SectorDamage.java b/core/src/mindustry/maps/SectorDamage.java
index 5f4e267d74..0400d28717 100644
--- a/core/src/mindustry/maps/SectorDamage.java
+++ b/core/src/mindustry/maps/SectorDamage.java
@@ -3,11 +3,19 @@ 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.type.*;
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 +23,293 @@ 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 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 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 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 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 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){
+ //scale health based on armor - yes, this is inaccurate, but better than nothing
+ float healthMult = 1f + Mathf.clamp(unit.armor / 20f);
+
+ sumHealth += unit.health*healthMult + 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 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){
+ float healthMult = 1f + Mathf.clamp(group.type.armor / 20f);
+ StatusEffect effect = (group.effect == null ? StatusEffects.none : group.effect);
+ int spawned = group.getSpawned(wave);
+ if(spawned <= 0) continue;
+ sumWaveHealth += spawned * (group.getShield(wave) + group.type.health * effect.healthMultiplier * healthMult);
+ sumWaveDps += spawned * group.type.dpsEstimate * effect.damageMultiplier;
+ }
+ 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;
+
+ //enemy units like to aim for a lot of non-essential things, so increase resulting health slightly
+ info.sumHealth = sumHealth * 1.2f;
+ //players tend to have longer range units/turrets, so assume DPS is higher
+ info.sumDps = sumDps * 1.2f;
+ 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,23 +330,71 @@ public class SectorDamage{
if(core != null && !frontier.isEmpty()){
for(Tile spawner : frontier){
//find path from spawn to core
- //TODO this is broken
Seq 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 removal = new Seq<>();
- other.remove();
+ int radius = 3;
+
+ //only penetrate a certain % by health, not by distance
+ float totalHealth = damage >= 1f ? 1f : 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 || damage >= 1f); 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 && damage < 0.999f){
+ break out;
+ }
+ }
+ }
+ }
+ }
}
+
+ for(Building r : removal){
+ if(r.tile.build == r){
+ r.addPlan(false);
+ r.tile.remove();
+ }
+ }
+ }
+ }
+
+ //kill every core if damage is maximum
+ if(fraction >= 1){
+ for(Building c : state.rules.defaultTeam.cores().copy()){
+ c.tile.remove();
}
}
@@ -90,6 +433,7 @@ public class SectorDamage{
Effect.rubble(other.build.x, other.build.y, other.block().size);
}
+ other.build.addPlan(false);
other.remove();
}
}
diff --git a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java
index ee2f6b437c..7e2b5e9aaa 100644
--- a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java
+++ b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java
@@ -412,12 +412,12 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
if(sector.hasEnemyBase()){
basegen.generate(tiles, enemies.map(r -> tiles.getn(r.x, r.y)), tiles.get(spawn.x, spawn.y), state.rules.waveTeam, sector, difficulty);
- state.rules.attackMode = true;
+ state.rules.attackMode = sector.info.attack = true;
}else{
- state.rules.winWave = 15 * (int)Math.max(difficulty * 10, 1);
+ state.rules.winWave = sector.info.winWave = 10 + 5 * (int)Math.max(difficulty * 10, 1);
}
- state.rules.waves = true;
+ state.rules.waves = sector.info.waves = true;
//TODO better waves
state.rules.spawns = DefaultWaves.generate(difficulty);
diff --git a/core/src/mindustry/mod/ContentParser.java b/core/src/mindustry/mod/ContentParser.java
index b9c119ee32..c8085ae8c8 100644
--- a/core/src/mindustry/mod/ContentParser.java
+++ b/core/src/mindustry/mod/ContentParser.java
@@ -260,8 +260,8 @@ public class ContentParser{
//TODO test this!
read(() -> {
//add reconstructor type
- if(value.hasChild("requirements")){
- JsonValue rec = value.remove("requirements");
+ if(value.has("requirements")){
+ JsonValue rec = value.remove("requirements");
//intermediate class for parsing
class UnitReq{
@@ -286,6 +286,17 @@ public class ContentParser{
}
+ //read extra default waves
+ if(value.has("waves")){
+ JsonValue waves = value.remove("waves");
+ SpawnGroup[] groups = parser.readValue(SpawnGroup[].class, waves);
+ for(SpawnGroup group : groups){
+ group.type = unit;
+ }
+
+ Vars.defaultWaves.get().addAll(groups);
+ }
+
readFields(unit, value, true);
});
diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java
index 12c33c7480..a596f3e7ae 100644
--- a/core/src/mindustry/mod/Mods.java
+++ b/core/src/mindustry/mod/Mods.java
@@ -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.");
diff --git a/core/src/mindustry/net/NetworkIO.java b/core/src/mindustry/net/NetworkIO.java
index 3a32d5cb6b..acaf9f9640 100644
--- a/core/src/mindustry/net/NetworkIO.java
+++ b/core/src/mindustry/net/NetworkIO.java
@@ -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();
diff --git a/core/src/mindustry/type/AmmoTypes.java b/core/src/mindustry/type/AmmoTypes.java
index 5b7c885519..534bdbe703 100644
--- a/core/src/mindustry/type/AmmoTypes.java
+++ b/core/src/mindustry/type/AmmoTypes.java
@@ -48,8 +48,8 @@ public class AmmoTypes implements ContentList{
if(build.block.consumes.hasPower() && build.block.consumes.getPower().buffered){
float amount = closest.build.power.status * build.block.consumes.getPower().capacity;
- float powerPerAmmo = totalPower / unit.type().ammoCapacity;
- float ammoRequired = unit.type().ammoCapacity - unit.ammo;
+ float powerPerAmmo = totalPower / unit.type.ammoCapacity;
+ float ammoRequired = unit.type.ammoCapacity - unit.ammo;
float powerRequired = ammoRequired * powerPerAmmo;
float powerTaken = Math.min(amount, powerRequired);
diff --git a/core/src/mindustry/type/ItemSeq.java b/core/src/mindustry/type/ItemSeq.java
index 0407f6a3f8..49055fb5b9 100644
--- a/core/src/mindustry/type/ItemSeq.java
+++ b/core/src/mindustry/type/ItemSeq.java
@@ -21,6 +21,13 @@ public class ItemSeq implements Iterable, Serializable{
stacks.each(this::add);
}
+ public ItemSeq copy(){
+ ItemSeq out = new ItemSeq();
+ out.total = total;
+ System.arraycopy(values, 0, out.values, 0, values.length);
+ return out;
+ }
+
public void each(ItemConsumer cons){
for(int i = 0; i < values.length; i++){
if(values[i] != 0){
@@ -46,6 +53,19 @@ public class ItemSeq implements Iterable, Serializable{
return values[item.id] > 0;
}
+ public boolean has(ItemSeq seq){
+ for(int i = 0; i < values.length; i++){
+ if(seq.values[i] > values[i]){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean has(Item item, int amount){
+ return values[item.id] >= amount;
+ }
+
public int get(Item item){
return values[item.id];
}
diff --git a/core/src/mindustry/type/ItemStack.java b/core/src/mindustry/type/ItemStack.java
index d5f4097b58..e2e0d2d5f4 100644
--- a/core/src/mindustry/type/ItemStack.java
+++ b/core/src/mindustry/type/ItemStack.java
@@ -64,6 +64,13 @@ public class ItemStack implements Comparable{
return item.compareTo(itemStack.item);
}
+ @Override
+ public boolean equals(Object o){
+ if(this == o) return true;
+ if(!(o instanceof ItemStack stack)) return false;
+ return amount == stack.amount && item == stack.item;
+ }
+
@Override
public String toString(){
return "ItemStack{" +
diff --git a/core/src/mindustry/type/Planet.java b/core/src/mindustry/type/Planet.java
index 6153aa4721..ad7fb066be 100644
--- a/core/src/mindustry/type/Planet.java
+++ b/core/src/mindustry/type/Planet.java
@@ -177,7 +177,7 @@ public class Planet extends UnlockableContent{
public void updateBaseCoverage(){
for(Sector sector : sectors){
float sum = 1f;
- for(Sector other : sector.inRange(2)){
+ for(Sector other : sector.near()){
if(other.generateEnemyBase){
sum += 1f;
}
@@ -204,6 +204,10 @@ public class Planet extends UnlockableContent{
@Override
public void init(){
+ for(Sector sector : sectors){
+ sector.loadInfo();
+ }
+
if(generator != null){
Noise.setSeed(id + 1);
@@ -264,4 +268,12 @@ public class Planet extends UnlockableContent{
public ContentType getContentType(){
return ContentType.planet;
}
+
+ public boolean visible(){
+ return true;
+ }
+
+ public void draw(Mat3D projection, Mat3D transform){
+ mesh.render(projection, transform);
+ }
}
diff --git a/core/src/mindustry/type/Sector.java b/core/src/mindustry/type/Sector.java
index 4c1df28945..975c8b82c8 100644
--- a/core/src/mindustry/type/Sector.java
+++ b/core/src/mindustry/type/Sector.java
@@ -7,6 +7,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.game.Saves.*;
+import mindustry.game.*;
import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.world.modules.*;
@@ -25,6 +26,7 @@ public class Sector{
public @Nullable SaveSlot save;
public @Nullable SectorPreset preset;
+ public SectorInfo info = new SectorInfo();
/** Number 0-1 indicating the difficulty based on nearby bases. */
public float baseCoverage;
@@ -38,60 +40,63 @@ public class Sector{
this.id = tile.id;
}
- public Seq inRange(int range){
- //TODO cleanup/remove
- if(true){
- tmpSeq1.clear();
- neighbors(tmpSeq1::add);
-
- return tmpSeq1;
+ /** @return a copy of the items in this sector - may be core items, or stored data. */
+ public ItemSeq getItems(){
+ if(isBeingPlayed()){
+ ItemSeq out = new ItemSeq();
+ if(state.rules.defaultTeam.core() != null) out.add(state.rules.defaultTeam.core().items);
+ return out;
+ }else{
+ return info.items;
}
-
- tmpSeq1.clear();
- tmpSeq2.clear();
- tmpSet.clear();
-
- tmpSeq1.add(this);
- tmpSet.add(this);
- for(int i = 0; i < range; i++){
- while(!tmpSeq1.isEmpty()){
- Sector sec = tmpSeq1.pop();
- tmpSet.add(sec);
- sec.neighbors(other -> {
- if(tmpSet.add(other)){
- tmpSeq2.add(other);
- }
- });
- }
- tmpSeq1.clear();
- tmpSeq1.addAll(tmpSeq2);
- }
-
- tmpSeq3.clear().addAll(tmpSeq2);
- return tmpSeq3;
}
- public void neighbors(Cons cons){
+ public Seq near(){
+ tmpSeq1.clear();
+ for(Ptile tile : tile.tiles){
+ tmpSeq1.add(planet.getSector(tile));
+ }
+
+ return tmpSeq1;
+ }
+
+ public void near(Cons cons){
for(Ptile tile : tile.tiles){
cons.get(planet.getSector(tile));
}
}
/** @return whether this sector can be landed on at all.
- * Only sectors adjacent to non-wave sectors can be landed on.
- * TODO also preset sectors*/
+ * Only sectors adjacent to non-wave sectors can be landed on. */
public boolean unlocked(){
return hasBase() || (preset != null && preset.alwaysUnlocked);
}
+ public void saveInfo(){
+ Core.settings.putJson(planet.name + "-s-" + id + "-info", info);
+ }
+
+ public void loadInfo(){
+ info = Core.settings.getJson(planet.name + "-s-" + id + "-info", SectorInfo.class, SectorInfo::new);
+ }
+
+ public float getProductionScale(){
+ return Math.max(1f - info.damage, 0);
+ }
+
+ public boolean isAttacked(){
+ if(isBeingPlayed()) return state.rules.waves;
+ return save != null && info.waves && info.hasCore;
+ }
+
/** @return whether the player has a base here. */
public boolean hasBase(){
- return save != null && !save.meta.tags.getBool("nocores");
+ return save != null && info.hasCore;
}
/** @return whether the enemy has a generated base here. */
public boolean hasEnemyBase(){
- return generateEnemyBase && (save == null || save.meta.rules.waves);
+ return generateEnemyBase && (save == null || info.waves);
}
public boolean isBeingPlayed(){
@@ -99,13 +104,18 @@ public class Sector{
return Vars.state.isGame() && Vars.state.rules.sector == this && !Vars.state.gameOver;
}
- public boolean isCaptured(){
- return save != null && !save.meta.rules.waves;
+ public String name(){
+ 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 boolean hasWaves(){
- return save != null && save.meta.rules.waves;
+ public void setName(String name){
+ info.name = name;
+ saveInfo();
+ }
+
+ public boolean isCaptured(){
+ return save != null && !info.waves;
}
public boolean hasSave(){
@@ -130,19 +140,16 @@ public class Sector{
return res % 2 == 0 ? res : res + 1;
}
- //TODO this should be stored in a more efficient structure, and be updated each turn
- public ItemSeq getExtraItems(){
- return Core.settings.getJson(key("extra-items"), ItemSeq.class, ItemSeq::new);
- }
-
- public void setExtraItems(ItemSeq stacks){
- Core.settings.putJson(key("extra-items"), stacks);
- }
-
public void addItem(Item item, int amount){
removeItem(item, -amount);
}
+ public void removeItems(ItemSeq items){
+ ItemSeq copy = items.copy();
+ copy.each((i, a) -> copy.set(i, -a));
+ addItems(copy);
+ }
+
public void removeItem(Item item, int amount){
ItemSeq seq = new ItemSeq();
seq.add(item, -amount);
@@ -156,137 +163,27 @@ public class Sector{
int cap = state.rules.defaultTeam.core().storageCapacity;
items.each((item, amount) -> storage.add(item, Math.min(cap - storage.get(item), amount)));
}
- }else{
- ItemSeq recv = getExtraItems();
-
- if(save != null){
- //"shave off" extra items
-
- ItemSeq count = new ItemSeq();
-
- //add items already present
- count.add(save.meta.secinfo.coreItems);
-
- count.add(calculateReceivedItems());
-
- int capacity = save.meta.secinfo.storageCapacity;
-
- //when over capacity, add that to the extra items
- count.each((i, a) -> {
- if(a > capacity){
- recv.remove(i, (a - capacity));
- }
- });
- }
-
- recv.add(items);
-
- setExtraItems(recv);
+ }else if(hasBase()){
+ items.each((item, amount) -> info.items.add(item, Math.min(info.storageCapacity - info.items.get(item), amount)));
+ saveInfo();
}
}
- public ItemSeq calculateItems(){
+ /** @return items currently in this sector, taking into account playing state. */
+ public ItemSeq items(){
ItemSeq count = new ItemSeq();
//for sectors being played on, add items directly
if(isBeingPlayed()){
count.add(state.rules.defaultTeam.items());
- }else if(save != null){
+ }else{
//add items already present
- count.add(save.meta.secinfo.coreItems);
-
- count.add(calculateReceivedItems());
-
- int capacity = save.meta.secinfo.storageCapacity;
-
- //validation
- count.each((item, amount) -> {
- //ensure positive items
- if(amount < 0) count.set(item, 0);
- //cap the items
- if(amount > capacity) count.set(item, capacity);
- });
+ count.add(info.items);
}
return count;
}
- public ItemSeq calculateReceivedItems(){
- ItemSeq count = new ItemSeq();
-
- if(save != null){
- long seconds = getSecondsPassed();
-
- //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(){
return planet.name + "#" + id;
}
diff --git a/core/src/mindustry/type/SectorPreset.java b/core/src/mindustry/type/SectorPreset.java
index 923d023b25..f08374fa70 100644
--- a/core/src/mindustry/type/SectorPreset.java
+++ b/core/src/mindustry/type/SectorPreset.java
@@ -23,6 +23,7 @@ public class SectorPreset extends UnlockableContent{
super(name);
this.generator = new FileMapGenerator(name);
this.planet = planet;
+ sector %= planet.sectors.size;
this.sector = planet.sectors.get(sector);
planet.preset(sector, this);
diff --git a/core/src/mindustry/type/StatusEffect.java b/core/src/mindustry/type/StatusEffect.java
index eb0bedb60c..90488c62ea 100644
--- a/core/src/mindustry/type/StatusEffect.java
+++ b/core/src/mindustry/type/StatusEffect.java
@@ -56,7 +56,7 @@ public class StatusEffect extends MappableContent{
}
if(effect != Fx.none && Mathf.chanceDelta(effectChance)){
- Tmp.v1.rnd(unit.type().hitSize /2f);
+ Tmp.v1.rnd(unit.type.hitSize /2f);
effect.at(unit.x + Tmp.v1.x, unit.y + Tmp.v1.y);
}
}
diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java
index 661e735fa7..7d0c3fd98b 100644
--- a/core/src/mindustry/type/UnitType.java
+++ b/core/src/mindustry/type/UnitType.java
@@ -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;
@@ -115,7 +117,7 @@ public class UnitType extends UnlockableContent{
public Unit create(Team team){
Unit unit = constructor.get();
unit.team = team;
- unit.type(this);
+ unit.setType(this);
unit.ammo = ammoCapacity; //fill up on ammo upon creation
unit.elevation = flying ? 1f : 0;
unit.heal();
@@ -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));
diff --git a/core/src/mindustry/ui/IntFormat.java b/core/src/mindustry/ui/IntFormat.java
index 2ca0e9de1f..fab10cd67f 100644
--- a/core/src/mindustry/ui/IntFormat.java
+++ b/core/src/mindustry/ui/IntFormat.java
@@ -10,7 +10,7 @@ import arc.func.*;
public class IntFormat{
private final StringBuilder builder = new StringBuilder();
private final String text;
- private int lastValue = Integer.MIN_VALUE;
+ private int lastValue = Integer.MIN_VALUE, lastValue2 = Integer.MIN_VALUE;
private Func converter = String::valueOf;
public IntFormat(String text){
@@ -30,4 +30,14 @@ public class IntFormat{
lastValue = value;
return builder;
}
+
+ public CharSequence get(int value1, int value2){
+ if(lastValue != value1 || lastValue2 != value2){
+ builder.setLength(0);
+ builder.append(Core.bundle.format(text, value1, value2));
+ }
+ lastValue = value1;
+ lastValue2 = value2;
+ return builder;
+ }
}
diff --git a/core/src/mindustry/ui/Styles.java b/core/src/mindustry/ui/Styles.java
index 13e97202e5..802a18004b 100644
--- a/core/src/mindustry/ui/Styles.java
+++ b/core/src/mindustry/ui/Styles.java
@@ -23,6 +23,7 @@ import static mindustry.gen.Tex.*;
@StyleDefaults
public class Styles{
+ //TODO all these names are inconsistent and not descriptive
public static Drawable black, black9, black8, black6, black3, black5, none, flatDown, flatOver;
public static ButtonStyle defaultb, waveb;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
diff --git a/core/src/mindustry/ui/dialogs/HostDialog.java b/core/src/mindustry/ui/dialogs/HostDialog.java
index 25300fc0b1..1282d47b7d 100644
--- a/core/src/mindustry/ui/dialogs/HostDialog.java
+++ b/core/src/mindustry/ui/dialogs/HostDialog.java
@@ -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);
}
diff --git a/core/src/mindustry/ui/dialogs/JoinDialog.java b/core/src/mindustry/ui/dialogs/JoinDialog.java
index 1c80418708..aa4d9c22c8 100644
--- a/core/src/mindustry/ui/dialogs/JoinDialog.java
+++ b/core/src/mindustry/ui/dialogs/JoinDialog.java
@@ -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);
}
diff --git a/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java b/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java
index acb240d7c9..62db818bf7 100644
--- a/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java
+++ b/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java
@@ -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.*;
@@ -29,18 +30,27 @@ public class LaunchLoadoutDialog extends BaseDialog{
super("@configure");
}
- public void show(CoreBlock core, Building build, Runnable confirm){
+ public void show(CoreBlock core, Sector sector, Runnable confirm){
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);
+ }
+ });
+
+ ItemSeq sitems = sector.getItems();
//updates sum requirements
Runnable update = () -> {
total.clear();
selected.requirements().each(total::add);
universe.getLaunchResources().each(total::add);
- valid = build.items.has(total);
+ valid = sitems.has(total);
};
Cons
rebuild = table -> {
@@ -57,8 +67,8 @@ public class LaunchLoadoutDialog extends BaseDialog{
String amountStr = "[lightgray]" + (al + " + [accent]" + as + "[lightgray]");
table.add(
- build.items.has(s.item, s.amount) ? amountStr :
- "[scarlet]" + (Math.min(build.items.get(s.item), s.amount) + "[lightgray]/" + amountStr)).padLeft(2).left().padRight(4);
+ sitems.has(s.item, s.amount) ? amountStr :
+ "[scarlet]" + (Math.min(sitems.get(s.item), s.amount) + "[lightgray]/" + amountStr)).padLeft(2).left().padRight(4);
if(++i % 4 == 0){
table.row();
@@ -79,7 +89,7 @@ public class LaunchLoadoutDialog extends BaseDialog{
update.run();
rebuildItems.run();
});
- });
+ }).width(204);
buttons.button("@launch.text", Icon.ok, () -> {
universe.updateLoadout(core, selected);
@@ -100,7 +110,7 @@ public class LaunchLoadoutDialog extends BaseDialog{
selected = s;
update.run();
rebuildItems.run();
- }).group(group).pad(4).disabled(!build.items.has(s.requirements())).checked(s == selected).size(200f);
+ }).group(group).pad(4).disabled(!sitems.has(s.requirements())).checked(s == selected).size(200f);
if(++i % cols == 0){
t.row();
diff --git a/core/src/mindustry/ui/dialogs/PausedDialog.java b/core/src/mindustry/ui/dialogs/PausedDialog.java
index f09e2fe185..fce68f3044 100644
--- a/core/src/mindustry/ui/dialogs/PausedDialog.java
+++ b/core/src/mindustry/ui/dialogs/PausedDialog.java
@@ -34,14 +34,6 @@ 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);
- cont.row();
-
float dw = 220f;
cont.defaults().width(dw).height(55).pad(5f);
@@ -86,10 +78,7 @@ public class PausedDialog extends BaseDialog{
cont.buttonRow("@load", Icon.download, load::show).disabled(b -> net.active());
}else if(state.isCampaign()){
- cont.buttonRow("@launchcore", Icon.up, () -> {
- hide();
- ui.planet.showLaunch(state.getSector(), player.team().core());
- }).disabled(b -> player.team().core() == null);
+ cont.buttonRow("@research", Icon.tree, ui.research::show);
cont.row();
diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java
index 4d24bbe8b8..6763ba37e5 100644
--- a/core/src/mindustry/ui/dialogs/PlanetDialog.java
+++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java
@@ -13,6 +13,7 @@ import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
+import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.game.*;
@@ -22,7 +23,6 @@ import mindustry.graphics.g3d.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.storage.*;
-import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
import static mindustry.graphics.g3d.PlanetRenderer.*;
@@ -40,7 +40,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
public int launchRange;
public float zoom = 1f, selectAlpha = 1f;
public @Nullable Sector selected, hovered, launchSector;
- public CoreBuild launcher;
public Mode mode = look;
public boolean launching;
public Cons listener = s -> {};
@@ -91,9 +90,16 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
mode = look;
selected = hovered = launchSector = null;
launching = false;
+
+ zoom = 1f;
+ planets.zoom = 1f;
+ selectAlpha = 0f;
+ launchSector = state.getSector();
+
if(planets.planet.getLastSector() != null){
lookAt(planets.planet.getLastSector());
}
+
return super.show();
}
@@ -106,7 +112,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
//update view to sector
lookAt(sector);
zoom = 1f;
- planets.zoom = 2f;
+ planets.zoom = 1f;
selectAlpha = 0f;
launchSector = sector;
@@ -115,37 +121,33 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
super.show();
}
- public void showLaunch(Sector sector, CoreBuild launcher){
- if(launcher == null) return;
-
- this.launcher = launcher;
- selected = null;
- hovered = null;
- launching = false;
-
- //update view to sector
- lookAt(sector);
- zoom = 1f;
- planets.zoom = 2f;
- selectAlpha = 0f;
- launchRange = ((CoreBlock)launcher.block).launchRange;
- launchSector = sector;
-
- mode = launch;
-
- super.show();
- }
-
- private void lookAt(Sector sector){
+ void lookAt(Sector sector){
planets.camPos.set(Tmp.v33.set(sector.tile.v).rotate(Vec3.Y, -sector.planet.getRotation()));
}
boolean canSelect(Sector sector){
if(mode == select) return sector.hasBase();
- return mode == launch &&
- (sector.tile.v.within(launchSector.tile.v, (launchRange + 0.5f) * planets.planet.sectorApproxRadius*2) //within range
- || (sector.preset != null && sector.preset.unlocked())); //is an unlocked preset
+ return sector.near().contains(Sector::hasBase)//(sector.tile.v.within(launchSector.tile.v, (launchRange + 0.5f) * planets.planet.sectorApproxRadius*2) //within range
+ || (sector.preset != null && sector.preset.unlocked()); //is an unlocked preset
+ }
+
+ Sector findLauncher(Sector to){
+ //directly nearby.
+ if(to.near().contains(launchSector)) return launchSector;
+
+ Sector launchFrom = launchSector;
+ if(launchFrom == null){
+ //TODO pick one with the most resources
+ launchFrom = to.near().find(Sector::hasBase);
+ if(launchFrom == null && to.preset != null){
+ if(launchSector != null) return launchSector;
+ launchFrom = planets.planet.sectors.min(s -> !s.hasBase() ? Float.MAX_VALUE : s.tile.v.dst2(to.tile.v));
+ if(!launchFrom.hasBase()) launchFrom = null;
+ }
+ }
+
+ return launchFrom;
}
@Override
@@ -157,9 +159,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
if(selectAlpha > 0.01f){
if(canSelect(sec) || sec.unlocked()){
- if(sec.baseCoverage > 0){
- planets.fill(sec, Tmp.c1.set(Team.crux.color).a(0.5f * sec.baseCoverage * selectAlpha), -0.002f);
- }
Color color =
sec.hasBase() ? Team.sharded.color :
@@ -177,8 +176,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
}
- if(launchSector != null){
- planets.fill(launchSector, hoverColor, -0.001f);
+ Sector current = state.getSector() != null && state.getSector().isBeingPlayed() ? state.getSector() : null;
+
+ if(current != null){
+ planets.fill(current, hoverColor, -0.001f);
}
//draw hover border
@@ -195,9 +196,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
planets.batch.flush(Gl.triangles);
- if(mode == launch || mode == select){
- if(hovered != launchSector && hovered != null && canSelect(hovered)){
- planets.drawArc(planet, launchSector.tile.v, hovered.tile.v);
+ if(hovered != null && !hovered.hasBase()){
+ Sector launchFrom = findLauncher(hovered);
+ if(launchFrom != null && hovered != launchFrom && canSelect(hovered)){
+ planets.drawArc(planet, launchFrom.tile.v, hovered.tile.v);
}
}
@@ -217,9 +219,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.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){
Draw.rect(icon, 0, 0);
@@ -244,7 +246,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
addListener(new ElementGestureListener(){
@Override
public void tap(InputEvent event, float x, float y, int count, KeyCode button){
- if(hovered != null && ((mode == launch ? canSelect(hovered) && hovered != launchSector : hovered.unlocked()) || debugSelect)){
+ if(hovered != null && ((mode == look ? canSelect(hovered) && hovered != launchSector : hovered.unlocked()) || debugSelect)){
selected = hovered;
}
@@ -263,9 +265,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
},
new Table(t -> {
t.touchable = Touchable.disabled;
- //TODO localize
t.top();
- t.label(() -> mode == select ? "@sectors.select" : mode == launch ? "Select Launch Sector" : "").style(Styles.outlineLabel).color(Pal.accent);
+ t.label(() -> mode == select ? "@sectors.select" : "").style(Styles.outlineLabel).color(Pal.accent);
}),
new Table(t -> {
t.right();
@@ -322,7 +323,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
stable.toFront();
//smooth camera toward the sector
- if(mode == launch && launching){
+ if(mode == look && launching){
float len = planets.camPos.len();
planets.camPos.slerp(Tmp.v31.set(selected.tile.v).rotate(Vec3.Y,-selected.planet.getRotation()).setLength(len), 0.1f);
}
@@ -352,69 +353,76 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
stable.clear();
stable.background(Styles.black6);
- stable.add("[accent]" + (sector.preset == null ? sector.id : sector.preset.localizedName)).row();
+ stable.table(title -> {
+ title.add("[accent]" + sector.name());
+ if(sector.preset == null){
+ title.button(Icon.pencilSmall, Styles.clearPartiali, () -> {
+ ui.showTextInput("@sectors.rename", "@name", 20, sector.name(), v -> {
+ sector.setName(v);
+ updateSelected();
+ });
+ }).size(40f).padLeft(4);
+ }
+ }).row();
+
stable.image().color(Pal.accent).fillX().height(3f).pad(3f).row();
stable.add(sector.save != null ? sector.save.getPlayTime() : "@sectors.unexplored").row();
- if(sector.hasWaves() || sector.hasEnemyBase()){
+
+ if(sector.isAttacked() || 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.isAttacked()){
//TODO localize when finalized
//these mechanics are likely to change and as such are not added to the bundle
stable.add("[scarlet]Under attack!");
stable.row();
- stable.add("[accent]" + Mathf.ceil(sectorDestructionTurns - (sector.getSecondsPassed() * 60) / turnDuration) + " turn(s)\nuntil destruction");
+ stable.add("[accent]" + (int)(sector.info.damage * 100) + "% damaged");
stable.row();
- }*/
+ }
- if(sector.save != null){
+ if(sector.save != null && sector.info.resources.any()){
stable.add("@sectors.resources").row();
stable.table(t -> {
-
- if(sector.save != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.resources.any()){
- t.left();
- int idx = 0;
- int max = 5;
- for(UnlockableContent c : sector.save.meta.secinfo.resources){
- t.image(c.icon(Cicon.small)).padRight(3);
- if(++idx % max == 0) t.row();
- }
- }else{
- t.add("@unknown").color(Color.lightGray);
+ t.left();
+ int idx = 0;
+ int max = 5;
+ for(UnlockableContent c : sector.info.resources){
+ t.image(c.icon(Cicon.small)).padRight(3);
+ if(++idx % max == 0) t.row();
}
-
-
}).fillX().row();
}
//production
- if(sector.hasBase() && sector.save.meta.hasProduction){
- stable.add("@sectors.production").row();
- stable.table(t -> {
- t.left();
+ if(sector.hasBase()){
+ Table t = new Table().left();
- sector.save.meta.secinfo.production.each((item, stat) -> {
- int total = (int)(stat.mean * 60);
- if(total > 1){
- t.image(item.icon(Cicon.small)).padRight(3);
- t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray);
- t.row();
- }
- });
- }).row();
+ float scl = sector.getProductionScale();
+
+ sector.info.production.each((item, stat) -> {
+ int total = (int)(stat.mean * 60 * scl);
+ if(total > 1){
+ t.image(item.icon(Cicon.small)).padRight(3);
+ t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray);
+ t.row();
+ }
+ });
+
+ if(t.getChildren().any()){
+ stable.add("@sectors.production").row();
+ stable.add(t).row();
+ }
}
//stored resources
- if(sector.hasBase() && sector.save.meta.secinfo.coreItems.total > 0){
+ if(sector.hasBase() && sector.info.items.total > 0){
stable.add("@sectors.stored").row();
stable.table(t -> {
t.left();
t.table(res -> {
- ItemSeq items = sector.calculateItems();
+ ItemSeq items = sector.items();
int i = 0;
for(ItemStack stack : items){
@@ -446,16 +454,20 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
}
- if(mode == launch && !sector.hasBase()){
- Sector current = state.rules.sector;
+ if(mode == look && !sector.hasBase()){
shouldHide = false;
- loadouts.show((CoreBlock)launcher.block, launcher, () -> {
- control.handleLaunch(launcher);
+ Sector from = findLauncher(sector);
+ CoreBlock block = from.info.bestCoreType instanceof CoreBlock b ? b : (CoreBlock)Blocks.coreShard;
+
+ loadouts.show(block, from, () -> {
+ from.removeItems(universe.getLastLoadout().requirements());
+ from.removeItems(universe.getLaunchResources());
+
launching = true;
zoom = 0.5f;
ui.hudfrag.showLaunchDirect();
- Time.runTask(launchDuration, () -> control.playSector(current, sector));
+ Time.runTask(launchDuration, () -> control.playSector(from, sector));
});
}else if(mode == select){
listener.get(sector);
@@ -484,7 +496,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
selected = null;
}
}
-
}
});
@@ -494,8 +505,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
public enum Mode{
/** Look around for existing sectors. Can only deploy. */
look,
- /** Launch to a new location. */
- launch,
/** Select a sector for some purpose. */
select
}
diff --git a/core/src/mindustry/ui/dialogs/ResearchDialog.java b/core/src/mindustry/ui/dialogs/ResearchDialog.java
index 9c5e6fd3ed..b79aacbce5 100644
--- a/core/src/mindustry/ui/dialogs/ResearchDialog.java
+++ b/core/src/mindustry/ui/dialogs/ResearchDialog.java
@@ -60,7 +60,7 @@ public class ResearchDialog extends BaseDialog{
for(Planet planet : content.planets()){
for(Sector sector : planet.sectors){
if(sector.hasSave()){
- ItemSeq cached = sector.calculateItems();
+ ItemSeq cached = sector.items();
add(cached);
cache.put(sector, cached);
}
@@ -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();
}
diff --git a/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java
index 8dd9753fda..c00076d92b 100644
--- a/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java
+++ b/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java
@@ -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 -> {
@@ -292,7 +291,7 @@ public class SettingsMenuDialog extends SettingsDialog{
}
return s + "%";
});
- graphics.sliderPref("bridgeopacity", 75, 0, 100, 5, s -> s + "%");
+ graphics.sliderPref("bridgeopacity", 100, 0, 100, 5, s -> s + "%");
if(!mobile){
graphics.checkPref("vsync", true, b -> Core.graphics.setVSync(b));
@@ -340,9 +339,6 @@ public class SettingsMenuDialog extends SettingsDialog{
graphics.checkPref("smoothcamera", true);
graphics.checkPref("position", false);
graphics.checkPref("fps", false);
- if(!mobile){
- graphics.checkPref("blockselectkeys", true);
- }
graphics.checkPref("playerindicators", true);
graphics.checkPref("indicators", true);
graphics.checkPref("animatedwater", true);
diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java
index f56d507c4d..d51c83881d 100644
--- a/core/src/mindustry/ui/fragments/HudFragment.java
+++ b/core/src/mindustry/ui/fragments/HudFragment.java
@@ -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
@@ -71,12 +71,17 @@ public class HudFragment extends Fragment{
//TODO details and stuff
Events.on(SectorCaptureEvent.class, e ->{
//TODO localize
- showToast("Sector[accent] captured[]!");
+ showToast("Sector [accent]" + (e.sector.isBeingPlayed() ? "" : e.sector.name() + " ") + "[white]captured!");
});
//TODO localize
Events.on(SectorLoseEvent.class, e -> {
- showToast(Icon.warning, "Sector " + e.sector.id + " [scarlet]lost!");
+ showToast(Icon.warning, "Sector [accent]" + e.sector.name() + "[white] lost!");
+ });
+
+ //TODO localize
+ Events.on(SectorInvasionEvent.class, e -> {
+ showToast(Icon.warning, "Sector [accent]" + e.sector.name() + "[white] under attack!");
});
Events.on(ResetEvent.class, e -> {
@@ -589,6 +594,7 @@ public class HudFragment extends Fragment{
StringBuilder ibuild = new StringBuilder();
IntFormat wavef = new IntFormat("wave");
+ IntFormat wavefc = new IntFormat("wave.cap");
IntFormat enemyf = new IntFormat("wave.enemy");
IntFormat enemiesf = new IntFormat("wave.enemies");
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
@@ -706,7 +712,7 @@ public class HudFragment extends Fragment{
t.add(new SideBar(() -> player.unit().healthf(), () -> true, true)).width(bw).growY().padRight(pad);
t.image(() -> player.icon()).scaling(Scaling.bounded).grow().maxWidth(54f);
t.add(new SideBar(() -> player.dead() ? 0f : player.displayAmmo() ? player.unit().ammof() : player.unit().healthf(), () -> !player.displayAmmo(), false)).width(bw).growY().padLeft(pad).update(b -> {
- b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type().ammoType.color : Pal.health);
+ b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color : Pal.health);
});
t.getChildren().get(1).toFront();
@@ -714,7 +720,11 @@ public class HudFragment extends Fragment{
table.labelWrap(() -> {
builder.setLength(0);
- builder.append(wavef.get(state.wave));
+ if(state.rules.winWave > 1 && state.rules.winWave >= state.wave && state.isCampaign()){
+ builder.append(wavefc.get(state.wave, state.rules.winWave));
+ }else{
+ builder.append(wavef.get(state.wave));
+ }
builder.append("\n");
if(state.enemies > 0){
@@ -727,7 +737,7 @@ public class HudFragment extends Fragment{
}
if(state.rules.waveTimer){
- builder.append((logic.isWaitingWave() ? Core.bundle.get("wave.waveInProgress") : ( waitingf.get((int)(state.wavetime/60)))));
+ builder.append((logic.isWaitingWave() ? Core.bundle.get("wave.waveInProgress") : (waitingf.get((int)(state.wavetime/60)))));
}else if(state.enemies == 0){
builder.append(Core.bundle.get("waiting"));
}
diff --git a/core/src/mindustry/ui/fragments/MinimapFragment.java b/core/src/mindustry/ui/fragments/MinimapFragment.java
index 43ad3163b0..5acf3bc6d3 100644
--- a/core/src/mindustry/ui/fragments/MinimapFragment.java
+++ b/core/src/mindustry/ui/fragments/MinimapFragment.java
@@ -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;
}
}
diff --git a/core/src/mindustry/ui/fragments/PlacementFragment.java b/core/src/mindustry/ui/fragments/PlacementFragment.java
index 7b53a24012..25c009ff63 100644
--- a/core/src/mindustry/ui/fragments/PlacementFragment.java
+++ b/core/src/mindustry/ui/fragments/PlacementFragment.java
@@ -289,7 +289,7 @@ public class PlacementFragment extends Fragment{
topTable.table(header -> {
String keyCombo = "";
- if(!mobile && Core.settings.getBool("blockselectkeys")){
+ if(!mobile){
Seq blocks = getByCategory(currentCategory);
for(int i = 0; i < blocks.size; i++){
if(blocks.get(i) == displayBlock && (i + 1) / 10 - 1 < blockSelect.length){
diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java
index 95d9a426e0..6ccd5855d2 100644
--- a/core/src/mindustry/world/Block.java
+++ b/core/src/mindustry/world/Block.java
@@ -14,6 +14,7 @@ import arc.struct.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.annotations.Annotations.*;
+import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
@@ -35,8 +36,6 @@ import java.util.*;
import static mindustry.Vars.*;
public class Block extends UnlockableContent{
- public static final int crackRegions = 8, maxCrackSize = 9;
-
public boolean hasItems;
public boolean hasLiquids;
public boolean hasPower;
@@ -212,8 +211,6 @@ public class Block extends UnlockableContent{
public @Load("@-team") TextureRegion teamRegion;
public TextureRegion[] teamRegions;
- //TODO make this not static
- public static TextureRegion[][] cracks;
protected static final Seq tempTiles = new Seq<>();
protected static final Seq tempTileEnts = new Seq<>();
@@ -352,7 +349,7 @@ public class Block extends UnlockableContent{
Liquid liquid = consumes.get(ConsumeType.liquid).liquid;
current = entity -> liquid;
}else{
- current = entity -> entity.liquids.current();
+ current = entity -> entity.liquids == null ? Liquids.water : entity.liquids.current();
}
bars.add("liquid", entity -> new Bar(() -> entity.liquids.get(current.get(entity)) <= 0.001f ? Core.bundle.get("bar.liquid") : current.get(entity).localizedName,
() -> current.get(entity).barColor(), () -> entity.liquids.get(current.get(entity)) / liquidCapacity));
@@ -621,7 +618,7 @@ public class Block extends UnlockableContent{
public ItemStack[] researchRequirements(){
ItemStack[] out = new ItemStack[requirements.length];
for(int i = 0; i < out.length; i++){
- int quantity = 40 + Mathf.round(Mathf.pow(requirements[i].amount, 1.25f) * 20 * researchCostMultiplier, 10);
+ int quantity = 40 + Mathf.round(Mathf.pow(requirements[i].amount, 1.15f) * 20 * researchCostMultiplier, 10);
out[i] = new ItemStack(requirements[i].item, UI.roundAmount(quantity));
}
@@ -687,15 +684,6 @@ public class Block extends UnlockableContent{
public void load(){
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);
//load specific team regions
diff --git a/core/src/mindustry/world/Build.java b/core/src/mindustry/world/Build.java
index 070ddab553..98409e6d18 100644
--- a/core/src/mindustry/world/Build.java
+++ b/core/src/mindustry/world/Build.java
@@ -45,7 +45,7 @@ public class Build{
Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(tile, team, true)));
}
- /** Places a BuildBlock at this location. */
+ /** Places a ConstructBlock at this location. */
@Remote(called = Loc.server)
public static void beginPlace(Block result, Team team, int x, int y, int rotation){
if(!validPlace(result, team, x, y, rotation)){
diff --git a/core/src/mindustry/world/CachedTile.java b/core/src/mindustry/world/CachedTile.java
index 3c14ade132..5777ece759 100644
--- a/core/src/mindustry/world/CachedTile.java
+++ b/core/src/mindustry/world/CachedTile.java
@@ -21,7 +21,7 @@ public class CachedTile extends Tile{
}
@Override
- protected void changeEntity(Team team, Prov entityprov, int rotation){
+ protected void changeBuild(Team team, Prov entityprov, int rotation){
build = null;
Block block = block();
diff --git a/core/src/mindustry/world/Tile.java b/core/src/mindustry/world/Tile.java
index 7478f81db4..18696d8930 100644
--- a/core/src/mindustry/world/Tile.java
+++ b/core/src/mindustry/world/Tile.java
@@ -45,7 +45,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
this.block = wall;
//update entity and create it if needed
- changeEntity(Team.derelict, wall::newBuilding, 0);
+ changeBuild(Team.derelict, wall::newBuilding, 0);
changed();
}
@@ -186,7 +186,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
this.block = type;
preChanged();
- changeEntity(team, entityprov, (byte)Mathf.mod(rotation, 4));
+ changeBuild(team, entityprov, (byte)Mathf.mod(rotation, 4));
if(build != null){
build.team(team);
@@ -267,6 +267,10 @@ public class Tile implements Position, QuadTreeObject, Displayable{
Geometry.circle(x, y, world.width(), world.height(), radius, cons);
}
+ public void circle(int radius, Cons cons){
+ circle(radius, (x, y) -> cons.get(world.rawTile(x, y)));
+ }
+
public void recache(){
if(!headless && !world.isGenerating()){
renderer.blocks.floor.recacheTile(this);
@@ -332,6 +336,11 @@ public class Tile implements Position, QuadTreeObject, Displayable{
recache();
}
+ /** Sets the overlay without a recache. */
+ public void setOverlayQuiet(Block block){
+ this.overlay = (Floor)block;
+ }
+
public void clearOverlay(){
setOverlayID((short)0);
}
@@ -421,15 +430,15 @@ public class Tile implements Position, QuadTreeObject, Displayable{
getHitbox(rect);
}
- public Tile getNearby(Point2 relative){
+ public Tile nearby(Point2 relative){
return world.tile(x + relative.x, y + relative.y);
}
- public Tile getNearby(int dx, int dy){
+ public Tile nearby(int dx, int dy){
return world.tile(x + dx, y + dy);
}
- public Tile getNearby(int rotation){
+ public Tile nearby(int rotation){
if(rotation == 0) return world.tile(x + 1, y);
if(rotation == 1) return world.tile(x, y + 1);
if(rotation == 2) return world.tile(x - 1, y);
@@ -437,7 +446,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
return null;
}
- public Building getNearbyEntity(int rotation){
+ public Building nearbyBuild(int rotation){
if(rotation == 0) return world.build(x + 1, y);
if(rotation == 1) return world.build(x, y + 1);
if(rotation == 2) return world.build(x - 1, y);
@@ -494,7 +503,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
}
}
- protected void changeEntity(Team team, Prov entityprov, int rotation){
+ protected void changeBuild(Team team, Prov entityprov, int rotation){
if(build != null){
int size = build.block.size;
build.remove();
diff --git a/core/src/mindustry/world/blocks/Autotiler.java b/core/src/mindustry/world/blocks/Autotiler.java
index 2989436fac..027ac93b31 100644
--- a/core/src/mindustry/world/blocks/Autotiler.java
+++ b/core/src/mindustry/world/blocks/Autotiler.java
@@ -133,7 +133,7 @@ public interface Autotiler{
for(int i = 0; i < 4; i++){
int realDir = Mathf.mod(rotation - i, 4);
- if(blends(tile, rotation, directional, i, world) && (tile != null && tile.getNearbyEntity(realDir) != null && !tile.getNearbyEntity(realDir).block.squareSprite)){
+ if(blends(tile, rotation, directional, i, world) && (tile != null && tile.nearbyBuild(realDir) != null && !tile.nearbyBuild(realDir).block.squareSprite)){
blendresult[4] |= (1 << i);
}
}
@@ -194,7 +194,7 @@ public interface Autotiler{
// TODO docs -- use for direction?
default boolean blends(Tile tile, int rotation, int direction){
- Building other = tile.getNearbyEntity(Mathf.mod(rotation - direction, 4));
+ Building other = tile.nearbyBuild(Mathf.mod(rotation - direction, 4));
return other != null && other.team == tile.team() && blends(tile, rotation, other.tileX(), other.tileY(), other.rotation, other.block);
}
diff --git a/core/src/mindustry/world/blocks/ConstructBlock.java b/core/src/mindustry/world/blocks/ConstructBlock.java
index c228397a32..cefdfa6887 100644
--- a/core/src/mindustry/world/blocks/ConstructBlock.java
+++ b/core/src/mindustry/world/blocks/ConstructBlock.java
@@ -42,7 +42,7 @@ public class ConstructBlock extends Block{
consBlocks[size - 1] = this;
}
- /** Returns a BuildBlock by size. */
+ /** Returns a ConstructBlock by size. */
public static ConstructBlock get(int size){
if(size > maxSize) throw new IllegalArgumentException("No. Don't place ConstructBlock of size greater than " + maxSize);
return consBlocks[size - 1];
diff --git a/core/src/mindustry/world/blocks/ControlBlock.java b/core/src/mindustry/world/blocks/ControlBlock.java
index 6724ff9205..7688cdab6a 100644
--- a/core/src/mindustry/world/blocks/ControlBlock.java
+++ b/core/src/mindustry/world/blocks/ControlBlock.java
@@ -10,4 +10,14 @@ public interface ControlBlock{
default boolean isControlled(){
return unit().isPlayer();
}
+
+ /** @return whether this block can be controlled at all. */
+ default boolean canControl(){
+ return true;
+ }
+
+ /** @return whether targets should automatically be selected (on mobile) */
+ default boolean shouldAutoTarget(){
+ return true;
+ }
}
diff --git a/core/src/mindustry/world/blocks/campaign/LaunchPad.java b/core/src/mindustry/world/blocks/campaign/LaunchPad.java
index 093fff4f70..726e14cd8a 100644
--- a/core/src/mindustry/world/blocks/campaign/LaunchPad.java
+++ b/core/src/mindustry/world/blocks/campaign/LaunchPad.java
@@ -115,16 +115,16 @@ public class LaunchPad extends Block{
public void display(Table table){
super.display(table);
+ if(!state.isCampaign()) return;
+
table.row();
table.label(() -> {
Sector dest = state.secinfo.getRealDestination();
return Core.bundle.format("launch.destination",
dest == null ? Core.bundle.get("sectors.nonelaunch") :
- dest.preset == null ?
- "[accent]Sector " + dest.id :
- "[accent]" + dest.preset.localizedName);
- }).pad(4);
+ "[accent]" + dest.name());
+ }).pad(4).wrap().width(200f).left();
}
@Override
@@ -213,7 +213,7 @@ public class LaunchPad extends Block{
//actually launch the items upon removal
if(team() == state.rules.defaultTeam){
if(destsec != null && (destsec != state.rules.sector || net.client())){
- ItemSeq dest = destsec.getExtraItems();
+ ItemSeq dest = new ItemSeq();
for(ItemStack stack : stacks){
dest.add(stack);
@@ -223,7 +223,7 @@ public class LaunchPad extends Block{
Events.fire(new LaunchItemEvent(stack));
}
- destsec.setExtraItems(dest);
+ destsec.addItems(dest);
}
}
}
diff --git a/core/src/mindustry/world/blocks/defense/ForceProjector.java b/core/src/mindustry/world/blocks/defense/ForceProjector.java
index b508e90965..3b90533745 100644
--- a/core/src/mindustry/world/blocks/defense/ForceProjector.java
+++ b/core/src/mindustry/world/blocks/defense/ForceProjector.java
@@ -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();
diff --git a/core/src/mindustry/world/blocks/defense/MendProjector.java b/core/src/mindustry/world/blocks/defense/MendProjector.java
index a2efa6e8b0..2bc50d6d7a 100644
--- a/core/src/mindustry/world/blocks/defense/MendProjector.java
+++ b/core/src/mindustry/world/blocks/defense/MendProjector.java
@@ -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);
diff --git a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java
index 56010c7c0f..ae3c10f3fd 100644
--- a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java
+++ b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java
@@ -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());
diff --git a/core/src/mindustry/world/blocks/defense/TractorBeamTurret.java b/core/src/mindustry/world/blocks/defense/TractorBeamTurret.java
index 1f74360ed5..3808f7c360 100644
--- a/core/src/mindustry/world/blocks/defense/TractorBeamTurret.java
+++ b/core/src/mindustry/world/blocks/defense/TractorBeamTurret.java
@@ -75,7 +75,7 @@ public class TractorBeamTurret extends Block{
}
//look at target
- if(target != null && target.within(this, range) && target.team() != team && target.type().flying && efficiency() > 0.01f){
+ if(target != null && target.within(this, range) && target.team() != team && target.type.flying && efficiency() > 0.01f){
any = true;
float dest = angleTo(target);
rotation = Angles.moveToward(rotation, dest, rotateSpeed * edelta());
diff --git a/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java b/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java
index ea94a670b9..521e6dc19b 100644
--- a/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java
+++ b/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java
@@ -19,7 +19,6 @@ import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
public class ItemTurret extends Turret{
- public int maxAmmo = 30;
public ObjectMap- ammoTypes = new ObjectMap<>();
public ItemTurret(String name){
diff --git a/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java b/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java
index e34d221d53..11d6213aa5 100644
--- a/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java
+++ b/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java
@@ -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;
}
diff --git a/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java b/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java
index f8b49195da..85e5243e2f 100644
--- a/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java
+++ b/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java
@@ -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
diff --git a/core/src/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/mindustry/world/blocks/defense/turrets/Turret.java
index 411ee782ec..5e1212ace1 100644
--- a/core/src/mindustry/world/blocks/defense/turrets/Turret.java
+++ b/core/src/mindustry/world/blocks/defense/turrets/Turret.java
@@ -12,6 +12,7 @@ import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
+import mindustry.core.*;
import mindustry.entities.*;
import mindustry.entities.Units.*;
import mindustry.entities.bullet.*;
@@ -41,6 +42,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;
@@ -167,7 +169,7 @@ public abstract class Turret extends Block{
@Override
public void control(LAccess type, double p1, double p2, double p3, double p4){
if(type == LAccess.shoot && !unit.isPlayer()){
- targetPos.set((float)p1, (float)p2);
+ targetPos.set(World.unconv((float)p1), World.unconv((float)p2));
logicControlTime = logicControlCooldown;
logicShooting = !Mathf.zero(p3);
}
@@ -192,6 +194,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;
diff --git a/core/src/mindustry/world/blocks/distribution/Conveyor.java b/core/src/mindustry/world/blocks/distribution/Conveyor.java
index 457e7e65e7..60ab45f159 100644
--- a/core/src/mindustry/world/blocks/distribution/Conveyor.java
+++ b/core/src/mindustry/world/blocks/distribution/Conveyor.java
@@ -156,7 +156,7 @@ public class Conveyor extends Block implements Autotiler{
lastInserted = build.lastInserted;
mid = build.mid;
minitem = build.minitem;
- items.addAll(build.items);
+ items.add(build.items);
}
}
diff --git a/core/src/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/mindustry/world/blocks/distribution/ItemBridge.java
index 2d09c69eba..87fcb11a7e 100644
--- a/core/src/mindustry/world/blocks/distribution/ItemBridge.java
+++ b/core/src/mindustry/world/blocks/distribution/ItemBridge.java
@@ -202,7 +202,7 @@ public class ItemBridge extends Block{
for(int i = 1; i <= range; i++){
for(int j = 0; j < 4; j++){
- Tile other = tile.getNearby(Geometry.d4[j].x * i, Geometry.d4[j].y * i);
+ Tile other = tile.nearby(Geometry.d4[j].x * i, Geometry.d4[j].y * i);
if(linkValid(tile, other)){
boolean linked = other.pos() == link;
diff --git a/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java b/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java
index 7c0bbb7821..08035d9c84 100644
--- a/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java
+++ b/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java
@@ -87,7 +87,7 @@ public class PayloadConveyor extends Block{
}
int ntrns = 1 + size/2;
- Tile next = tile.getNearby(Geometry.d4(rotation).x * ntrns, Geometry.d4(rotation).y * ntrns);
+ Tile next = tile.nearby(Geometry.d4(rotation).x * ntrns, Geometry.d4(rotation).y * ntrns);
blocked = (next != null && next.solid() && !next.block().outputsPayload) || (this.next != null && (this.next.rotation + 2)%4 == rotation);
}
diff --git a/core/src/mindustry/world/blocks/distribution/Router.java b/core/src/mindustry/world/blocks/distribution/Router.java
index 5b107d5a0e..766a1bafff 100644
--- a/core/src/mindustry/world/blocks/distribution/Router.java
+++ b/core/src/mindustry/world/blocks/distribution/Router.java
@@ -1,9 +1,12 @@
package mindustry.world.blocks.distribution;
+import arc.math.*;
+import arc.util.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
+import mindustry.world.blocks.*;
import mindustry.world.meta.*;
public class Router extends Block{
@@ -20,10 +23,30 @@ public class Router extends Block{
noUpdateDisabled = true;
}
- public class RouterBuild extends Building{
+ public class RouterBuild extends Building implements ControlBlock{
public Item lastItem;
public Tile lastInput;
public float time;
+ public @Nullable BlockUnitc unit;
+
+ @Override
+ public Unit unit(){
+ if(unit == null){
+ unit = (BlockUnitc)UnitTypes.block.create(team);
+ unit.tile(this);
+ }
+ return (Unit)unit;
+ }
+
+ @Override
+ public boolean canControl(){
+ return size == 1;
+ }
+
+ @Override
+ public boolean shouldAutoTarget(){
+ return false;
+ }
@Override
public void updateTile(){
@@ -72,6 +95,23 @@ public class Router extends Block{
}
public Building getTileTarget(Item item, Tile from, boolean set){
+ if(unit != null && isControlled()){
+ unit.health(health);
+ unit.ammo(unit.type().ammoCapacity * (items.total() > 0 ? 1f : 0f));
+ unit.team(team);
+
+ int angle = Mathf.mod((int)((angleTo(unit.aimX(), unit.aimY()) + 45) / 90), 4);
+
+ if(unit.isShooting()){
+ Building other = nearby(angle);
+ if(other != null && other.acceptItem(this, item)){
+ return other;
+ }
+ }
+
+ return null;
+ }
+
int counter = rotation;
for(int i = 0; i < proximity.size; i++){
Building other = proximity.get((i + counter) % proximity.size);
diff --git a/core/src/mindustry/world/blocks/distribution/StackConveyor.java b/core/src/mindustry/world/blocks/distribution/StackConveyor.java
index e7095a686c..3327c62c15 100644
--- a/core/src/mindustry/world/blocks/distribution/StackConveyor.java
+++ b/core/src/mindustry/world/blocks/distribution/StackConveyor.java
@@ -203,7 +203,7 @@ public class StackConveyor extends Block implements Autotiler{
if(front() instanceof StackConveyorBuild e && e.team == team){
// sleep if its occupied
if(e.link == -1){
- e.items.addAll(items);
+ e.items.add(items);
e.lastItem = lastItem;
e.link = tile.pos();
// ▲ to | from ▼
diff --git a/core/src/mindustry/world/blocks/environment/Floor.java b/core/src/mindustry/world/blocks/environment/Floor.java
index a70056717a..cacf375cef 100644
--- a/core/src/mindustry/world/blocks/environment/Floor.java
+++ b/core/src/mindustry/world/blocks/environment/Floor.java
@@ -183,7 +183,7 @@ public class Floor extends Block{
for(int i = 0; i < 8; i++){
Point2 point = Geometry.d8[i];
- Tile other = tile.getNearby(point);
+ Tile other = tile.nearby(point);
if(other != null && other.floor().cacheLayer == layer && other.floor().edges() != null){
if(!blended.getAndSet(other.floor().id)){
blenders.add(other.floor());
@@ -200,7 +200,7 @@ public class Floor extends Block{
for(int i = 0; i < 8; i++){
Point2 point = Geometry.d8[i];
- Tile other = tile.getNearby(point);
+ Tile other = tile.nearby(point);
if(other != null && doEdge(other.floor()) && other.floor().cacheLayer == cacheLayer && other.floor().edges() != null){
if(!blended.getAndSet(other.floor().id)){
blenders.add(other.floor());
@@ -217,7 +217,7 @@ public class Floor extends Block{
for(Block block : blenders){
for(int i = 0; i < 8; i++){
Point2 point = Geometry.d8[i];
- Tile other = tile.getNearby(point);
+ Tile other = tile.nearby(point);
if(other != null && other.floor() == block){
TextureRegion region = edge((Floor)block, 1 - point.x, 1 - point.y);
Draw.rect(region, tile.worldx(), tile.worldy());
@@ -229,7 +229,7 @@ public class Floor extends Block{
//'new' style of edges with shadows instead of colors, not used currently
protected void drawEdgesFlat(Tile tile, boolean sameLayer){
for(int i = 0; i < 4; i++){
- Tile other = tile.getNearby(i);
+ Tile other = tile.nearby(i);
if(other != null && doEdge(other.floor())){
Color color = other.floor().mapColor;
Draw.color(color.r, color.g, color.b, 1f);
diff --git a/core/src/mindustry/world/blocks/environment/StaticTree.java b/core/src/mindustry/world/blocks/environment/StaticTree.java
index 78c6133d2f..9b1a7063f4 100644
--- a/core/src/mindustry/world/blocks/environment/StaticTree.java
+++ b/core/src/mindustry/world/blocks/environment/StaticTree.java
@@ -21,7 +21,7 @@ public class StaticTree extends StaticWall{
float oy = 0;
for(int i = 0; i < 4; i++){
- if(tile.getNearby(i) != null && tile.getNearby(i).block() instanceof StaticWall){
+ if(tile.nearby(i) != null && tile.nearby(i).block() instanceof StaticWall){
if(i == 0){
r.setWidth(r.width - crop);
diff --git a/core/src/mindustry/world/blocks/experimental/BlockForge.java b/core/src/mindustry/world/blocks/experimental/BlockForge.java
index 52509674a7..251c6820a0 100644
--- a/core/src/mindustry/world/blocks/experimental/BlockForge.java
+++ b/core/src/mindustry/world/blocks/experimental/BlockForge.java
@@ -99,7 +99,7 @@ public class BlockForge extends PayloadAcceptor{
public void buildConfiguration(Table table){
Seq blocks = Vars.content.blocks().select(b -> b.isVisible() && b.size <= 2);
- ItemSelection.buildTable(table, blocks, () -> recipe, block -> recipe = block);
+ ItemSelection.buildTable(table, blocks, () -> recipe, this::configure);
}
@Override
diff --git a/core/src/mindustry/world/blocks/logic/LogicBlock.java b/core/src/mindustry/world/blocks/logic/LogicBlock.java
index aecf5d4149..0f2fce78ba 100644
--- a/core/src/mindustry/world/blocks/logic/LogicBlock.java
+++ b/core/src/mindustry/world/blocks/logic/LogicBlock.java
@@ -8,6 +8,7 @@ import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
+import mindustry.core.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
@@ -304,8 +305,8 @@ public class LogicBlock extends Block{
}
asm.getVar("@this").value = this;
- asm.putConst("@thisx", x);
- asm.putConst("@thisy", y);
+ asm.putConst("@thisx", World.conv(x));
+ asm.putConst("@thisy", World.conv(y));
executor.load(asm);
}catch(Exception e){
diff --git a/core/src/mindustry/world/blocks/logic/SwitchBlock.java b/core/src/mindustry/world/blocks/logic/SwitchBlock.java
index bca47a306a..d27cebb1b3 100644
--- a/core/src/mindustry/world/blocks/logic/SwitchBlock.java
+++ b/core/src/mindustry/world/blocks/logic/SwitchBlock.java
@@ -37,6 +37,11 @@ public class SwitchBlock extends Block{
}
}
+ @Override
+ public Boolean config(){
+ return enabled;
+ }
+
@Override
public byte version(){
return 1;
diff --git a/core/src/mindustry/world/blocks/payloads/UnitPayload.java b/core/src/mindustry/world/blocks/payloads/UnitPayload.java
index 724df792fa..2292244864 100644
--- a/core/src/mindustry/world/blocks/payloads/UnitPayload.java
+++ b/core/src/mindustry/world/blocks/payloads/UnitPayload.java
@@ -43,7 +43,7 @@ public class UnitPayload implements Payload{
@Override
public boolean dump(){
- if(!Units.canCreate(unit.team, unit.type())){
+ if(!Units.canCreate(unit.team, unit.type)){
deactiveTime = 1f;
return false;
}
@@ -74,7 +74,7 @@ public class UnitPayload implements Payload{
@Override
public void draw(){
Drawf.shadow(unit.x, unit.y, 20);
- Draw.rect(unit.type().icon(Cicon.full), unit.x, unit.y, unit.rotation - 90);
+ Draw.rect(unit.type.icon(Cicon.full), unit.x, unit.y, unit.rotation - 90);
//draw warning
if(deactiveTime > 0){
diff --git a/core/src/mindustry/world/blocks/production/Fracker.java b/core/src/mindustry/world/blocks/production/Fracker.java
index d894cf05b5..b1ea3a5b34 100644
--- a/core/src/mindustry/world/blocks/production/Fracker.java
+++ b/core/src/mindustry/world/blocks/production/Fracker.java
@@ -45,7 +45,7 @@ public class Fracker extends SolidPump{
Draw.rect(region, x, y);
super.drawCracks();
- Drawf.liquid(liquidRegion, x, y, liquids.total() / liquidCapacity, result.color);
+ Drawf.liquid(liquidRegion, x, y, liquids.get(result) / liquidCapacity, result.color);
Draw.rect(rotatorRegion, x, y, pumpTime);
Draw.rect(topRegion, x, y);
diff --git a/core/src/mindustry/world/blocks/production/PayloadAcceptor.java b/core/src/mindustry/world/blocks/production/PayloadAcceptor.java
index 75cfde865f..3b4693c250 100644
--- a/core/src/mindustry/world/blocks/production/PayloadAcceptor.java
+++ b/core/src/mindustry/world/blocks/production/PayloadAcceptor.java
@@ -95,7 +95,7 @@ public class PayloadAcceptor extends Block{
updatePayload();
payRotation = Mathf.slerpDelta(payRotation, rotate ? rotdeg() : 90f, 0.3f);
- payVector.approachDelta(Vec2.ZERO, payloadSpeed);
+ payVector.approach(Vec2.ZERO, payloadSpeed * delta());
return hasArrived();
}
@@ -105,7 +105,7 @@ public class PayloadAcceptor extends Block{
updatePayload();
- payVector.trns(rotdeg(), payVector.len() + edelta() * payloadSpeed);
+ payVector.trns(rotdeg(), payVector.len() + delta() * payloadSpeed);
payRotation = rotdeg();
if(payVector.len() >= size * tilesize/2f){
diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java
index bdb16eb674..1acf5d61c3 100644
--- a/core/src/mindustry/world/blocks/storage/CoreBlock.java
+++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java
@@ -196,6 +196,21 @@ public class CoreBlock extends StorageBlock{
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
public void drawLight(){
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
public void placed(){
super.placed();
@@ -335,7 +333,7 @@ public class CoreBlock extends StorageBlock{
@Override
public void itemTaken(Item item){
- if(state.isCampaign()){
+ if(state.isCampaign() && team == state.rules.defaultTeam){
//update item taken amount
state.secinfo.handleCoreItem(item, -1);
}
@@ -344,6 +342,9 @@ public class CoreBlock extends StorageBlock{
@Override
public void handleItem(Building source, Item item){
if(net.server() || !net.active()){
+ if(team == state.rules.defaultTeam){
+ state.secinfo.handleCoreItem(item, 1);
+ }
if(items.get(item) >= getMaximumAccepted(item)){
//create item incineration effect at random intervals
diff --git a/core/src/mindustry/world/blocks/storage/StorageBlock.java b/core/src/mindustry/world/blocks/storage/StorageBlock.java
index 50eb65a270..71045e166b 100644
--- a/core/src/mindustry/world/blocks/storage/StorageBlock.java
+++ b/core/src/mindustry/world/blocks/storage/StorageBlock.java
@@ -26,7 +26,7 @@ public class StorageBlock extends Block{
}
public static void incinerateEffect(Building self, Building source){
- if(Mathf.chance(0.1)){
+ if(Mathf.chance(0.3)){
Tile edge = Edges.getFacingEdge(source, self);
Tile edge2 = Edges.getFacingEdge(self, source);
if(edge != null && edge2 != null){
@@ -46,7 +46,9 @@ public class StorageBlock extends Block{
@Override
public void handleItem(Building source, Item item){
if(linkedCore != null){
- incinerateEffect(this, source);
+ if(linkedCore.items.get(item) >= ((CoreBuild)linkedCore).storageCapacity){
+ incinerateEffect(this, source);
+ }
((CoreBuild)linkedCore).noEffect = true;
linkedCore.handleItem(source, item);
}else{
@@ -70,7 +72,7 @@ public class StorageBlock extends Block{
public void overwrote(Seq previous){
for(Building other : previous){
if(other.items != null){
- items.addAll(other.items);
+ items.add(other.items);
}
}
diff --git a/core/src/mindustry/world/blocks/units/Reconstructor.java b/core/src/mindustry/world/blocks/units/Reconstructor.java
index 5b5696bf4c..ed21b6dc11 100644
--- a/core/src/mindustry/world/blocks/units/Reconstructor.java
+++ b/core/src/mindustry/world/blocks/units/Reconstructor.java
@@ -64,6 +64,22 @@ public class Reconstructor extends UnitBlock{
super.setStats();
stats.add(BlockStat.productionTime, constructTime / 60f, StatUnit.seconds);
+ stats.add(BlockStat.output, table -> {
+ table.row();
+ for(var upgrade : upgrades){
+ float size = 8*3;
+ if(upgrade[0].unlockedNow() && upgrade[1].unlockedNow()){
+ table.image(upgrade[0].icon(Cicon.small)).size(size).padRight(4).padLeft(10).scaling(Scaling.fit).right();
+ table.add(upgrade[0].localizedName).left();
+
+ table.add("[lightgray] -> ");
+
+ table.image(upgrade[1].icon(Cicon.small)).size(size).padRight(4).scaling(Scaling.fit);
+ table.add(upgrade[1].localizedName).left();
+ table.row();
+ }
+ }
+ });
}
@Override
@@ -90,7 +106,7 @@ public class Reconstructor extends UnitBlock{
return this.payload == null
&& relativeTo(source) != rotation
&& payload instanceof UnitPayload
- && hasUpgrade(((UnitPayload)payload).unit.type());
+ && hasUpgrade(((UnitPayload)payload).unit.type);
}
@Override
@@ -114,9 +130,9 @@ public class Reconstructor extends UnitBlock{
if(constructing() && hasArrived()){
Draw.draw(Layer.blockOver, () -> {
Draw.alpha(1f - progress/ constructTime);
- Draw.rect(payload.unit.type().icon(Cicon.full), x, y, rotdeg() - 90);
+ Draw.rect(payload.unit.type.icon(Cicon.full), x, y, rotdeg() - 90);
Draw.reset();
- Drawf.construct(this, upgrade(payload.unit.type()), rotdeg() - 90f, progress / constructTime, speedScl, time);
+ Drawf.construct(this, upgrade(payload.unit.type), rotdeg() - 90f, progress / constructTime, speedScl, time);
});
}else{
Draw.z(Layer.blockOver);
@@ -135,7 +151,7 @@ public class Reconstructor extends UnitBlock{
if(payload != null){
//check if offloading
- if(!hasUpgrade(payload.unit.type())){
+ if(!hasUpgrade(payload.unit.type)){
moveOutPayload();
}else{ //update progress
if(moveInPayload()){
@@ -146,7 +162,7 @@ public class Reconstructor extends UnitBlock{
//upgrade the unit
if(progress >= constructTime){
- payload.unit = upgrade(payload.unit.type()).create(payload.unit.team());
+ payload.unit = upgrade(payload.unit.type).create(payload.unit.team());
progress = 0;
Effect.shake(2f, 3f, this);
Fx.producesmoke.at(this);
@@ -168,12 +184,12 @@ public class Reconstructor extends UnitBlock{
public UnitType unit(){
if(payload == null) return null;
- UnitType t = upgrade(payload.unit.type());
+ UnitType t = upgrade(payload.unit.type);
return t != null && t.unlockedNow() ? t : null;
}
public boolean constructing(){
- return payload != null && hasUpgrade(payload.unit.type());
+ return payload != null && hasUpgrade(payload.unit.type);
}
public boolean hasUpgrade(UnitType type){
diff --git a/core/src/mindustry/world/blocks/units/ResupplyPoint.java b/core/src/mindustry/world/blocks/units/ResupplyPoint.java
index 2714be2358..6402b20147 100644
--- a/core/src/mindustry/world/blocks/units/ResupplyPoint.java
+++ b/core/src/mindustry/world/blocks/units/ResupplyPoint.java
@@ -65,10 +65,10 @@ public class ResupplyPoint extends Block{
public static boolean resupply(Team team, float x, float y, float range, float ammoAmount, Color ammoColor, Boolf valid){
if(!state.rules.unitAmmo) return false;
- Unit unit = Units.closest(team, x, y, range, u -> u.type().ammoType instanceof ItemAmmoType && u.ammo <= u.type().ammoCapacity - ammoAmount && valid.get(u));
+ Unit unit = Units.closest(team, x, y, range, u -> u.type.ammoType instanceof ItemAmmoType && u.ammo <= u.type.ammoCapacity - ammoAmount && valid.get(u));
if(unit != null){
Fx.itemTransfer.at(x, y, ammoAmount / 2f, ammoColor, unit);
- unit.ammo = Math.min(unit.ammo + ammoAmount, unit.type().ammoCapacity);
+ unit.ammo = Math.min(unit.ammo + ammoAmount, unit.type.ammoCapacity);
return true;
}
diff --git a/core/src/mindustry/world/blocks/units/UnitFactory.java b/core/src/mindustry/world/blocks/units/UnitFactory.java
index 74248f9932..52a3af85c7 100644
--- a/core/src/mindustry/world/blocks/units/UnitFactory.java
+++ b/core/src/mindustry/world/blocks/units/UnitFactory.java
@@ -14,6 +14,7 @@ import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
+import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.*;
@@ -122,6 +123,12 @@ public class UnitFactory extends UnitBlock{
return currentPlan == -1 ? 0 : progress / plans.get(currentPlan).time;
}
+ @Override
+ public Object senseObject(LAccess sensor){
+ if(sensor == LAccess.config) return currentPlan == -1 ? null : plans.get(currentPlan).unit;
+ return super.senseObject(sensor);
+ }
+
@Override
public void buildConfiguration(Table table){
Seq units = Seq.with(plans).map(u -> u.unit).filter(u -> u.unlockedNow());
diff --git a/core/src/mindustry/world/modules/ItemModule.java b/core/src/mindustry/world/modules/ItemModule.java
index 16d49484f0..6cd96985cb 100644
--- a/core/src/mindustry/world/modules/ItemModule.java
+++ b/core/src/mindustry/world/modules/ItemModule.java
@@ -243,6 +243,16 @@ public class ItemModule extends BlockModule{
}
}
+ public void add(ItemSeq stacks){
+ stacks.each(this::add);
+ }
+
+ public void add(ItemModule items){
+ for(int i = 0; i < items.items.length; i++){
+ add(i, items.items[i]);
+ }
+ }
+
public void add(Item item, int amount){
add(item.id, amount);
}
@@ -261,12 +271,6 @@ public class ItemModule extends BlockModule{
}
}
- public void addAll(ItemModule items){
- for(int i = 0; i < items.items.length; i++){
- add(i, items.items[i]);
- }
- }
-
public void remove(Item item, int amount){
amount = Math.min(amount, items[item.id]);
diff --git a/desktop/src/mindustry/desktop/DesktopLauncher.java b/desktop/src/mindustry/desktop/DesktopLauncher.java
index bd9ec3e81b..76b72e3ca3 100644
--- a/desktop/src/mindustry/desktop/DesktopLauncher.java
+++ b/desktop/src/mindustry/desktop/DesktopLauncher.java
@@ -227,7 +227,9 @@ public class DesktopLauncher extends ClientLauncher{
@Override
public void updateLobby(){
- SVars.net.updateLobby();
+ if(SVars.net != null){
+ SVars.net.updateLobby();
+ }
}
@Override
diff --git a/desktop/src/mindustry/desktop/steam/SStats.java b/desktop/src/mindustry/desktop/steam/SStats.java
index 7e7c9ecef3..925c670223 100644
--- a/desktop/src/mindustry/desktop/steam/SStats.java
+++ b/desktop/src/mindustry/desktop/steam/SStats.java
@@ -60,7 +60,7 @@ public class SStats implements SteamUserStatsCallback{
// active10Phantoms.complete();
//}
- if(Groups.unit.count(u -> u.type() == UnitTypes.crawler && u.team() == player.team()) >= 50){
+ if(Groups.unit.count(u -> u.type == UnitTypes.crawler && u.team() == player.team()) >= 50){
active50Crawlers.complete();
}
diff --git a/fastlane/metadata/android/en-US/changelogs/29672.txt b/fastlane/metadata/android/en-US/changelogs/29672.txt
new file mode 100644
index 0000000000..04c1a277ad
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/29672.txt
@@ -0,0 +1,6 @@
+- Fixed "host game" crash, as well as some other prominent bugs
+- Logic: Breaking change: All coordinates are now in tiles, not in "world units"
+- Logic: Added sensors for ammo (units/turrets)
+- Campaign: Added research button in mobile multiplayer (check pause menu)
+- Campaign: Added tech tree sharing in multiplayer - only the host can research
+- Campaign: Re-added sector invasions, any sector near an enemy base can be invaded
diff --git a/gradle.properties b/gradle.properties
index 5a37b2f0ab..0ebd9ffd28 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
org.gradle.daemon=true
org.gradle.jvmargs=-Xms256m -Xmx1024m
-archash=dfcb9ab4b9f9bb977ed3cff4b8a16c22e076368a
+archash=46ebdb4aeb1e03ca6b3b4c27a93533dc92278a33
diff --git a/jitpack.yml b/jitpack.yml
new file mode 100644
index 0000000000..e1dfa83815
--- /dev/null
+++ b/jitpack.yml
@@ -0,0 +1,4 @@
+before_install:
+ - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
+ - source install-jdk.sh --feature 14
+ - jshell --version
\ No newline at end of file
diff --git a/tests/src/test/java/SectorTests.java b/tests/src/test/java/SectorTests.java
index 4110670eed..6f07e77313 100644
--- a/tests/src/test/java/SectorTests.java
+++ b/tests/src/test/java/SectorTests.java
@@ -66,7 +66,7 @@ public class SectorTests{
outer:
for(int i = 1; i <= 1000; i++){
for(SpawnGroup spawn : spawns){
- if(spawn.effect == StatusEffects.boss && spawn.getUnitsSpawned(i) > 0){
+ if(spawn.effect == StatusEffects.boss && spawn.getSpawned(i) > 0){
bossWave = i;
break outer;
}
@@ -84,7 +84,7 @@ public class SectorTests{
for(int i = 1; i <= bossWave; i++){
int total = 0;
for(SpawnGroup spawn : spawns){
- total += spawn.getUnitsSpawned(i);
+ total += spawn.getSpawned(i);
}
assertNotEquals(0, total, "Sector " + zone + " has no spawned enemies at wave " + i);
diff --git a/tests/src/test/java/power/FakeGraphics.java b/tests/src/test/java/power/FakeGraphics.java
index 378f675224..5f64aa61f3 100644
--- a/tests/src/test/java/power/FakeGraphics.java
+++ b/tests/src/test/java/power/FakeGraphics.java
@@ -63,11 +63,6 @@ public class FakeGraphics extends Graphics{
return 0;
}
- @Override
- public float getRawDeltaTime(){
- return 0;
- }
-
@Override
public int getFramesPerSecond(){
return 0;
diff --git a/tools/src/mindustry/tools/Generators.java b/tools/src/mindustry/tools/Generators.java
index d8f67eee8c..645b2ea7f0 100644
--- a/tools/src/mindustry/tools/Generators.java
+++ b/tools/src/mindustry/tools/Generators.java
@@ -153,9 +153,9 @@ public class Generators{
ImagePacker.generate("cracks", () -> {
RidgedPerlin r = new RidgedPerlin(1, 3);
- for(int size = 1; size <= Block.maxCrackSize; size++){
+ for(int size = 1; size <= BlockRenderer.maxCrackSize; size++){
int dim = size * 32;
- int steps = Block.crackRegions;
+ int steps = BlockRenderer.crackRegions;
for(int i = 0; i < steps; i++){
float fract = i / (float)steps;