Merge branch 'master' into schematic

This commit is contained in:
J-VdS
2020-08-19 16:14:48 +02:00
420 changed files with 16090 additions and 10744 deletions

View File

@@ -33,6 +33,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void setup(){
loadLogger();
loader = new LoadRenderer();
Events.fire(new ClientCreateEvent());

View File

@@ -41,7 +41,7 @@ public class Vars implements Loadable{
/** Maximum schematic size.*/
public static final int maxSchematicSize = 32;
/** All schematic base64 starts with this string.*/
public static final String schematicBaseStart ="bXNjaAB";
public static final String schematicBaseStart ="bXNjaA";
/** IO buffer size. */
public static final int bufferSize = 8192;
/** global charset, since Android doesn't support the Charsets class */

View File

@@ -5,11 +5,13 @@ import arc.func.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -41,6 +43,8 @@ public class WaveSpawner{
spawning = true;
for(SpawnGroup group : state.rules.spawns){
if(group.type == null) continue;
int spawned = group.getUnitsSpawned(state.wave - 1);
if(group.type.flying){
@@ -125,14 +129,18 @@ public class WaveSpawner{
}
private void spawnEffect(Unit unit){
Fx.unitSpawn.at(unit.x(), unit.y(), 0f, unit);
Time.run(30f, () -> {
unit.add();
Fx.spawn.at(unit);
});
Call.spawnEffect(unit.x, unit.y, unit.type());
Time.run(30f, unit::add);
}
private interface SpawnConsumer{
void accept(float x, float y, boolean shockwave);
}
@Remote(called = Loc.server, unreliable = true)
public static void spawnEffect(float x, float y, UnitType type){
Fx.unitSpawn.at(x, y, 0f, type);
Time.run(30f, () -> Fx.spawn.at(x, y));
}
}

View File

@@ -11,4 +11,6 @@ import arc.math.geom.*;
public interface FormationMember{
/** Returns the target location of this formation member. */
Vec3 formationPos();
float formationSize();
}

View File

@@ -12,6 +12,8 @@ import arc.math.geom.*;
*/
public abstract class FormationPattern{
public int slots;
/** Spacing between members. */
public float spacing = 20f;
/** Returns the location of the given slot index. */
public abstract Vec3 calculateSlotLocation(Vec3 out, int slot);

View File

@@ -0,0 +1,26 @@
package mindustry.ai.formations.patterns;
import arc.math.geom.*;
import mindustry.ai.formations.*;
public class ArrowFormation extends FormationPattern{
//total triangular numbers
private static final int totalTris = 30;
//triangular number table
private static final int[] triTable = new int[totalTris];
//calculat triangular numbers
static{
int sum = 0;
for(int i = 0; i < totalTris; i++){
triTable[i] = sum;
sum += (i + 1);
}
}
@Override
public Vec3 calculateSlotLocation(Vec3 out, int slot){
//TODO
return out;
}
}

View File

@@ -5,7 +5,6 @@ import arc.math.geom.*;
import mindustry.ai.formations.*;
public class SquareFormation extends FormationPattern{
public float spacing = 20;
@Override
public Vec3 calculateSlotLocation(Vec3 out, int slot){

View File

@@ -1,9 +1,6 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.entities.units.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
@@ -22,6 +19,8 @@ public class BuilderAI extends AIController{
builder.lookAt(builder.vel().angle());
}
builder.updateBuilding(true);
//approach request if building
if(builder.buildPlan() != null){
BuildPlan req = builder.buildPlan();
@@ -63,19 +62,4 @@ public class BuilderAI extends AIController{
}
}
}
protected void moveTo(Position target, float circleLength){
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
}

View File

@@ -1,93 +1,49 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
public class FlyingAI extends AIController{
@Override
public void updateUnit(){
public void updateMovement(){
if(unit.moving()){
unit.rotation(unit.vel().angle());
unit.lookAt(unit.vel.angle());
}
if(unit.isFlying()){
unit.wobble();
}
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y())){
target = null;
}
if(retarget()){
targetClosest();
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
}
boolean shoot = false;
if(target != null && unit.hasWeapons()){
if(unit.type().weapons.first().rotate){
moveTo(unit.range() * 0.85f);
moveTo(target, unit.range() * 0.8f);
unit.lookAt(target);
}else{
attack(80f);
}
shoot = unit.inRange(target);
if(shoot && unit.type().hasWeapons()){
Vec2 to = Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed);
unit.aim(to);
}
}
}
unit.controlWeapons(shoot, shoot);
@Override
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
Teamc result = target(x, y, range, air, ground);
if(result != null) return result;
if(ground) result = targetFlag(x, y, BlockFlag.producer, true);
if(result != null) return result;
if(ground) result = targetFlag(x, y, BlockFlag.turret, true);
if(result != null) return result;
return null;
}
//TODO clean up
protected void circle(float circleLength){
circle(circleLength, unit.type().speed);
}
protected void circle(float circleLength, float speed){
if(target == null) return;
vec.set(target).sub(unit);
if(vec.len() < circleLength){
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed * Time.delta);
unit.moveAt(vec);
}
protected void moveTo(float circleLength){
if(target == null) return;
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
protected void attack(float circleLength){
vec.set(target).sub(unit);

View File

@@ -60,6 +60,15 @@ public class FormationAI extends AIController implements FormationMember{
}
}
@Override
public float formationSize(){
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
//TODO return formation size
//eturn ((Commanderc)unit).formation().
}
return unit.hitSize * 1.7f;
}
@Override
public boolean isBeingControlled(Unit player){
return leader == player;

View File

@@ -12,15 +12,7 @@ import static mindustry.Vars.pathfinder;
public class GroundAI extends AIController{
@Override
public void updateUnit(){
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
target = null;
}
if(retarget()){
targetClosest();
}
public void updateMovement(){
Building core = unit.closestEnemyCore();
@@ -34,20 +26,13 @@ public class GroundAI extends AIController{
}
}
boolean rotate = false, shoot = false;
if(!Units.invalidateTarget(target, unit, unit.range())){
rotate = true;
shoot = unit.within(target, unit.range());
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
unit.controlWeapons(rotate, shoot);
}
protected void moveToCore(FlagTarget path){

View File

@@ -0,0 +1,88 @@
package mindustry.ai.types;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class MinerAI extends AIController{
boolean mining = true;
Item targetItem;
Tile ore;
@Override
protected void updateMovement(){
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
if(unit.isFlying()){
unit.wobble();
}
Building core = unit.closestCore();
if(!(unit instanceof Minerc) || core == null) return;
Minerc miner = (Minerc)unit;
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));
//core full of the target item, do nothing
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
unit.clearItem();
return;
}
//if inventory is full, drop it off.
if(unit.stack.amount >= unit.type().itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
mining = false;
}else{
if(retarget() && targetItem != null){
ore = indexer.findClosestOre(unit.x, unit.y, targetItem);
}
if(ore != null){
moveTo(ore, unit.type().range / 1.5f);
if(unit.within(ore, unit.type().range)){
miner.mineTile(ore);
}
if(ore.block() != Blocks.air){
mining = false;
}
}
}
}else{
if(unit.stack.amount == 0){
mining = true;
return;
}
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);
}
unit.clearItem();
mining = true;
}
circle(core, unit.type().range / 1.8f);
}
}
@Override
protected void updateTargeting(){
}
}

View File

@@ -17,7 +17,7 @@ public class SuicideAI extends GroundAI{
}
if(retarget()){
targetClosest();
target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
}
Building core = unit.closestEnemyCore();

View File

@@ -68,6 +68,8 @@ public class PhysicsProcess implements AsyncProcess{
PhysicRef ref = entity.physref();
if(ref.wasGround != grounded){
if(ref.body.getFixtureList().isEmpty()) continue;
//set correct filter
ref.body.getFixtureList().first().setFilterData(grounded ? ground : flying);
ref.wasGround = grounded;

View File

@@ -18,6 +18,8 @@ import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.experimental.*;
import mindustry.world.blocks.legacy.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.logic.MessageBlock;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.sandbox.*;
@@ -48,7 +50,7 @@ public class Blocks implements ContentList{
melter, separator, disassembler, sporePress, pulverizer, incinerator, coalCentrifuge,
//sandbox
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, message, illuminator,
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, illuminator,
//defense
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
@@ -80,8 +82,11 @@ public class Blocks implements ContentList{
additiveReconstructor, multiplicativeReconstructor, exponentialReconstructor, tetrativeReconstructor,
repairPoint, resupplyPoint,
//logic
message, switchBlock, microProcessor, logicProcessor, logicDisplay, memoryCell,
//campaign
launchPad, launchPadLarge, dataProcessor,
launchPad, launchPadLarge,
//misc experimental
blockForge, blockLoader, blockUnloader;
@@ -134,6 +139,7 @@ public class Blocks implements ContentList{
speedMultiplier = 0.2f;
variants = 0;
liquidDrop = Liquids.water;
liquidMultiplier = 1.5f;
isLiquid = true;
status = StatusEffects.wet;
statusDuration = 120f;
@@ -246,11 +252,13 @@ public class Blocks implements ContentList{
sand = new Floor("sand"){{
itemDrop = Items.sand;
playerUnmineable = true;
attributes.set(Attribute.oil, 0.7f);
}};
darksand = new Floor("darksand"){{
itemDrop = Items.sand;
playerUnmineable = true;
attributes.set(Attribute.oil, 1.5f);
}};
((ShallowLiquid)darksandTaintedWater).set(Blocks.taintedWater, Blocks.darksand);
@@ -267,7 +275,8 @@ public class Blocks implements ContentList{
salt = new Floor("salt"){{
variants = 0;
attributes.set(Attribute.water, -0.2f);
attributes.set(Attribute.water, -0.25f);
attributes.set(Attribute.oil, 0.3f);
}};
snow = new Floor("snow"){{
@@ -355,7 +364,7 @@ public class Blocks implements ContentList{
shale = new Floor("shale"){{
variants = 3;
attributes.set(Attribute.oil, 0.15f);
attributes.set(Attribute.oil, 1f);
}};
shaleRocks = new StaticWall("shalerocks"){{
@@ -498,8 +507,8 @@ public class Blocks implements ContentList{
siliconCrucible = new AttributeSmelter("silicon-crucible"){{
requirements(Category.crafting, with(Items.titanium, 120, Items.metaglass, 80, Items.plastanium, 35, Items.silicon, 60));
craftEffect = Fx.smeltsmoke;
outputItem = new ItemStack(Items.silicon, 5);
craftTime = 140f;
outputItem = new ItemStack(Items.silicon, 6);
craftTime = 90f;
size = 3;
hasPower = true;
hasLiquids = false;
@@ -885,10 +894,10 @@ public class Blocks implements ContentList{
requirements(Category.effect, with(Items.lead, 25, Items.silicon, 12));
hasShadow = false;
health = 40;
damage = 11;
damage = 23;
tileDamage = 7f;
length = 10;
tendrils = 5;
tendrils = 4;
}};
//endregion
@@ -1270,6 +1279,8 @@ public class Blocks implements ContentList{
size = 3;
liquidCapacity = 30f;
attribute = Attribute.oil;
baseEfficiency = 0f;
itemUseTime = 60f;
consumes.item(Items.sand);
consumes.power(3f);
@@ -1299,18 +1310,18 @@ public class Blocks implements ContentList{
itemCapacity = 9000;
size = 4;
unitCapModifier = 16;
unitCapModifier = 14;
}};
coreNucleus = new CoreBlock("core-nucleus"){{
requirements(Category.effect, with(Items.copper, 1000, Items.lead, 1000));
requirements(Category.effect, with(Items.copper, 8000, Items.lead, 8000, Items.silicon, 5000, Items.thorium, 4000));
unitType = UnitTypes.gamma;
health = 4000;
itemCapacity = 13000;
size = 5;
unitCapModifier = 24;
unitCapModifier = 20;
}};
vault = new StorageBlock("vault"){{
@@ -1441,7 +1452,7 @@ public class Blocks implements ContentList{
recoilAmount = 2f;
reloadTime = 90f;
cooldown = 0.03f;
powerUse = 2.5f;
powerUse = 6f;
shootShake = 2f;
shootEffect = Fx.lancerLaserShoot;
smokeEffect = Fx.none;
@@ -1460,6 +1471,7 @@ public class Blocks implements ContentList{
hitSize = 4;
lifetime = 16f;
drawSize = 400f;
collidesAir = false;
}};
}};
@@ -1473,7 +1485,7 @@ public class Blocks implements ContentList{
reloadTime = 35f;
shootCone = 40f;
rotatespeed = 8f;
powerUse = 1.5f;
powerUse = 5f;
targetAir = false;
range = 90f;
shootEffect = Fx.lightningShoot;
@@ -1496,7 +1508,7 @@ public class Blocks implements ContentList{
health = 160 * size * size;
rotateSpeed = 10;
consumes.power(3f);
consumes.powerCond(3f, (TractorBeamEntity e) -> e.target != null);
}};
swarmer = new ItemTurret("swarmer"){{
@@ -1690,7 +1702,7 @@ public class Blocks implements ContentList{
requirements(Category.units, with(Items.copper, 50, Items.lead, 120, Items.silicon, 80));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.dagger, 60f * 20, with(Items.silicon, 10, Items.lead, 10)),
new UnitPlan(UnitTypes.crawler, 60f * 15, with(Items.silicon, 10, Items.blastCompound, 10)),
new UnitPlan(UnitTypes.crawler, 60f * 15, with(Items.silicon, 10, Items.coal, 20)),
new UnitPlan(UnitTypes.nova, 60f * 40, with(Items.silicon, 30, Items.lead, 20, Items.titanium, 20)),
};
size = 3;
@@ -1700,7 +1712,7 @@ public class Blocks implements ContentList{
airFactory = new UnitFactory("air-factory"){{
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 10)),
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 15)),
new UnitPlan(UnitTypes.mono, 60f * 35, with(Items.silicon, 30, Items.lead, 15)),
};
size = 3;
@@ -1836,10 +1848,6 @@ public class Blocks implements ContentList{
alwaysUnlocked = true;
}};
message = new MessageBlock("message"){{
requirements(Category.effect, with(Items.graphite, 5));
}};
illuminator = new LightBlock("illuminator"){{
requirements(Category.effect, BuildVisibility.lightingOnly, with(Items.graphite, 12, Items.silicon, 8));
brightness = 0.67f;
@@ -1877,14 +1885,50 @@ public class Blocks implements ContentList{
consumes.power(6f);
}};
dataProcessor = new ResearchBlock("data-processor"){{
//requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 200, Items.lead, 100));
//endregion campaign
//region logic
size = 3;
alwaysUnlocked = true;
message = new MessageBlock("message"){{
requirements(Category.logic, with(Items.graphite, 5));
}};
//endregion campaign
switchBlock = new SwitchBlock("switch"){{
requirements(Category.logic, with(Items.graphite, 5));
}};
microProcessor = new LogicBlock("micro-processor"){{
requirements(Category.logic, with(Items.copper, 80, Items.lead, 50, Items.silicon, 60));
instructionsPerTick = 2;
size = 1;
}};
logicProcessor = new LogicBlock("logic-processor"){{
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 140, Items.graphite, 80, Items.thorium, 70));
instructionsPerTick = 5;
range = 16 * 10;
size = 2;
}};
logicDisplay = new LogicDisplay("logic-display"){{
requirements(Category.logic, with(Items.copper, 200, Items.lead, 120, Items.silicon, 100, Items.metaglass, 50));
displaySize = 80;
size = 3;
}};
memoryCell = new MemoryBlock("memory-cell"){{
requirements(Category.logic, with(Items.graphite, 40, Items.silicon, 40));
memoryCapacity = 64;
}};
//endregion
//region experimental
blockForge = new BlockForge("block-forge"){{

View File

@@ -191,7 +191,7 @@ public class Bullets implements ContentList{
fragGlass = new FlakBulletType(4f, 3){{
lifetime = 70f;
ammoMultiplier = 5f;
ammoMultiplier = 3f;
shootEffect = Fx.shootSmall;
reloadMultiplier = 0.8f;
width = 6f;
@@ -221,8 +221,8 @@ public class Bullets implements ContentList{
fragExplosive = new FlakBulletType(4f, 5){{
shootEffect = Fx.shootBig;
ammoMultiplier = 4f;
splashDamage = 15f;
splashDamageRadius = 34f;
splashDamage = 18f;
splashDamageRadius = 55f;
collidesGround = true;
status = StatusEffects.blasted;
@@ -230,7 +230,8 @@ public class Bullets implements ContentList{
}};
fragSurge = new FlakBulletType(4.5f, 13){{
splashDamage = 45f;
ammoMultiplier = 4f;
splashDamage = 50f;
splashDamageRadius = 40f;
lightning = 2;
lightningLength = 7;
@@ -239,7 +240,7 @@ public class Bullets implements ContentList{
explodeRange = 20f;
}};
missileExplosive = new MissileBulletType(2.7f, 10, "missile"){{
missileExplosive = new MissileBulletType(3f, 10){{
width = 8f;
height = 8f;
shrinkY = 0f;
@@ -247,7 +248,6 @@ public class Bullets implements ContentList{
splashDamageRadius = 30f;
splashDamage = 30f;
ammoMultiplier = 4f;
lifetime = 150f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
@@ -255,7 +255,7 @@ public class Bullets implements ContentList{
statusDuration = 60f;
}};
missileIncendiary = new MissileBulletType(2.9f, 12, "missile"){{
missileIncendiary = new MissileBulletType(3f, 12){{
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
width = 7f;
@@ -265,26 +265,24 @@ public class Bullets implements ContentList{
homingPower = 0.08f;
splashDamageRadius = 20f;
splashDamage = 20f;
lifetime = 160f;
hitEffect = Fx.blastExplosion;
status = StatusEffects.burning;
}};
missileSurge = new MissileBulletType(4.4f, 20, "bullet"){{
missileSurge = new MissileBulletType(3f, 20){{
width = 8f;
height = 8f;
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 28f;
splashDamage = 40f;
lifetime = 150f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
lightning = 2;
lightningLength = 14;
}};
standardCopper = new BasicBulletType(2.5f, 9, "bullet"){{
standardCopper = new BasicBulletType(2.5f, 9){{
width = 7f;
height = 9f;
lifetime = 60f;
@@ -293,7 +291,7 @@ public class Bullets implements ContentList{
ammoMultiplier = 2;
}};
standardDense = new BasicBulletType(3.5f, 18, "bullet"){{
standardDense = new BasicBulletType(3.5f, 18){{
width = 9f;
height = 12f;
reloadMultiplier = 0.6f;
@@ -430,34 +428,25 @@ public class Bullets implements ContentList{
}
};
basicFlame = new BulletType(3f, 15f){
{
ammoMultiplier = 3f;
hitSize = 7f;
lifetime = 42f;
pierce = true;
drag = 0.05f;
statusDuration = 60f * 4;
shootEffect = Fx.shootSmallFlame;
hitEffect = Fx.hitFlameSmall;
despawnEffect = Fx.none;
status = StatusEffects.burning;
keepVelocity = false;
hittable = false;
}
basicFlame = new BulletType(3.35f, 15f){{
ammoMultiplier = 3f;
hitSize = 7f;
lifetime = 18f;
pierce = true;
statusDuration = 60f * 4;
shootEffect = Fx.shootSmallFlame;
hitEffect = Fx.hitFlameSmall;
despawnEffect = Fx.none;
status = StatusEffects.burning;
keepVelocity = false;
hittable = false;
}};
@Override
public float range(){
return 50f;
}
};
pyraFlame = new BulletType(3.3f, 22f){{
pyraFlame = new BulletType(3.35f, 22f){{
ammoMultiplier = 4f;
hitSize = 7f;
lifetime = 42f;
lifetime = 18f;
pierce = true;
drag = 0.05f;
statusDuration = 60f * 6;
shootEffect = Fx.shootPyraFlame;
hitEffect = Fx.hitFlameSmall;

View File

@@ -26,15 +26,15 @@ public class Fx{
none = new Effect(0, 0f, e -> {}),
unitSpawn = new Effect(30f, e -> {
if(!(e.data instanceof Unit)) return;
if(!(e.data instanceof UnitType)) return;
alpha(e.fin());
float scl = 1f + e.fout() * 2f;
Unit unit = e.data();
rect(unit.type().region, e.x, e.y,
unit.type().region.getWidth() * Draw.scl * scl, unit.type().region.getHeight() * Draw.scl * scl, 180f);
UnitType unit = e.data();
rect(unit.region, e.x, e.y,
unit.region.getWidth() * Draw.scl * scl, unit.region.getHeight() * Draw.scl * scl, 180f);
}),
@@ -63,15 +63,15 @@ public class Fx{
}),
unitDespawn = new Effect(100f, e -> {
if(!(e.data instanceof Unitc)) return;
if(!(e.data instanceof Unit) || e.<Unit>data().type() == null) return;
Unitc select = (Unitc)e.data;
Unit select = e.data();
float scl = e.fout(Interp.pow2Out);
float p = Draw.scl;
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;
@@ -96,13 +96,13 @@ public class Fx{
Fill.square(x, y, 1f * size, 45f);
}),
itemTransfer = new Effect(10f, e -> {
itemTransfer = new Effect(12f, e -> {
if(!(e.data instanceof Position)) return;
Position to = e.data();
Tmp.v1.set(e.x, e.y).interpolate(Tmp.v2.set(to), e.fin(), Interp.pow3)
.add(Tmp.v2.sub(e.x, e.y).nor().rotate90(1).scl(Mathf.randomSeedRange(e.id, 1f) * e.fslope() * 10f));
float x = Tmp.v1.x, y = Tmp.v1.y;
float size = Math.min(0.8f + e.rotation / 5f, 2);
float size = 1f;
stroke(e.fslope() * 2f * size, Pal.accent);
Lines.circle(x, y, e.fslope() * 2f * size);
@@ -369,7 +369,7 @@ public class Fx{
hitLiquid = new Effect(16, e -> {
color(e.color);
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation + 180f, 60f, (x, y) -> {
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation, 60f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
});
@@ -654,13 +654,11 @@ public class Fx{
}),
wet = new Effect(40f, e -> {
wet = new Effect(80f, e -> {
color(Liquids.water.color);
alpha(Mathf.clamp(e.fin() * 2f));
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 1f);
});
Fill.circle(e.x, e.y, e.fout() * 1f);
}),
sapped = new Effect(40f, e -> {
@@ -672,6 +670,12 @@ public class Fx{
}),
sporeSlowed = new Effect(40f, e -> {
color(Pal.spore);
Fill.circle(e.x, e.y, e.fslope() * 1.1f);
}),
oily = new Effect(42f, e -> {
color(Liquids.oil.color);

View File

@@ -9,7 +9,7 @@ import mindustry.type.*;
public class Planets implements ContentList{
public static Planet
sun,
starter; //TODO rename
serpulo;
@Override
public void load(){
@@ -31,9 +31,8 @@ public class Planets implements ContentList{
);
}};
//TODO rename
starter = new Planet("TODO", sun, 3, 1){{
generator = new TODOPlanetGenerator();
serpulo = new Planet("serpulo", sun, 3, 1){{
generator = new SerpuloPlanetGenerator();
meshLoader = () -> new HexMesh(this, 6);
atmosphereColor = Color.valueOf("3c1b8f");
startSector = 15;

View File

@@ -1,162 +1,64 @@
package mindustry.content;
import mindustry.ctype.*;
import mindustry.game.Objectives.*;
import mindustry.type.*;
import static arc.struct.Seq.*;
import static mindustry.content.Planets.*;
public class SectorPresets implements ContentList{
public static SectorPreset
groundZero,
craters, frozenForest, ruinousShores, stainedMountains, tarFields, fungalPass,
saltFlats, overgrowth, impact0078, crags,
saltFlats, overgrowth,
desolateRift, nuclearComplex;
@Override
public void load(){
groundZero = new SectorPreset("groundZero", starter, 15){{
groundZero = new SectorPreset("groundZero", serpulo, 15){{
alwaysUnlocked = true;
conditionWave = 5;
launchPeriod = 5;
rules = r -> {
r.winWave = 20;
};
captureWave = 10;
}};
saltFlats = new SectorPreset("saltFlats", starter, 101){{
conditionWave = 10;
launchPeriod = 5;
requirements = with(
new SectorWave(groundZero, 60),
//new Unlock(Blocks.daggerFactory),
//new Unlock(Blocks.draugFactory),
new Research(Blocks.door),
new Research(Blocks.waterExtractor)
);
saltFlats = new SectorPreset("saltFlats", serpulo, 101){{
}};
frozenForest = new SectorPreset("frozenForest", starter, 86){{
conditionWave = 10;
requirements = with(
new SectorWave(groundZero, 10),
new Research(Blocks.junction),
new Research(Blocks.router)
);
frozenForest = new SectorPreset("frozenForest", serpulo, 86){{
captureWave = 40;
}};
craters = new SectorPreset("craters", starter, 18){{
conditionWave = 10;
requirements = with(
new SectorWave(frozenForest, 10),
new Research(Blocks.mender),
new Research(Blocks.combustionGenerator)
);
craters = new SectorPreset("craters", serpulo, 18){{
captureWave = 40;
}};
ruinousShores = new SectorPreset("ruinousShores", starter, 19){{
conditionWave = 20;
launchPeriod = 20;
requirements = with(
new SectorWave(groundZero, 20),
new SectorWave(craters, 15),
new Research(Blocks.graphitePress),
new Research(Blocks.combustionGenerator),
new Research(Blocks.kiln),
new Research(Blocks.mechanicalPump)
);
ruinousShores = new SectorPreset("ruinousShores", serpulo, 19){{
captureWave = 40;
}};
stainedMountains = new SectorPreset("stainedMountains", starter, 20){{
conditionWave = 10;
launchPeriod = 10;
requirements = with(
new SectorWave(frozenForest, 15),
new Research(Blocks.pneumaticDrill),
new Research(Blocks.powerNode),
new Research(Blocks.turbineGenerator)
);
stainedMountains = new SectorPreset("stainedMountains", serpulo, 20){{
captureWave = 30;
}};
fungalPass = new SectorPreset("fungalPass", starter, 21){{
requirements = with(
new SectorWave(stainedMountains, 15),
//new Unlock(Blocks.daggerFactory),
//new Unlock(Blocks.crawlerFactory),
new Research(Blocks.door),
new Research(Blocks.siliconSmelter)
);
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
}};
overgrowth = new SectorPreset("overgrowth", starter, 22){{
conditionWave = 12;
launchPeriod = 4;
requirements = with(
new SectorWave(craters, 40),
new Launched(fungalPass),
new Research(Blocks.cultivator),
new Research(Blocks.sporePress)
//new Unlock(Blocks.titanFactory),
//new Unlock(Blocks.wraithFactory)
);
overgrowth = new SectorPreset("overgrowth", serpulo, 22){{
}};
tarFields = new SectorPreset("tarFields", starter, 23){{
conditionWave = 15;
launchPeriod = 10;
requirements = with(
new SectorWave(ruinousShores, 20),
new Research(Blocks.coalCentrifuge),
new Research(Blocks.conduit),
new Research(Blocks.wave)
);
tarFields = new SectorPreset("tarFields", serpulo, 23){{
captureWave = 40;
}};
desolateRift = new SectorPreset("desolateRift", starter, 123){{
conditionWave = 3;
launchPeriod = 2;
requirements = with(
new SectorWave(tarFields, 20),
new Research(Blocks.thermalGenerator),
new Research(Blocks.thoriumReactor)
);
desolateRift = new SectorPreset("desolateRift", serpulo, 123){{
captureWave = 40;
}};
nuclearComplex = new SectorPreset("nuclearComplex", starter, 130){{
conditionWave = 30;
launchPeriod = 15;
requirements = with(
new Launched(fungalPass),
new Research(Blocks.thermalGenerator),
new Research(Blocks.laserDrill)
);
nuclearComplex = new SectorPreset("nuclearComplex", serpulo, 130){{
captureWave = 60;
}};
/*
crags = new Zone("crags", new MapGenerator("crags").dist(2f)){{
loadout = Loadouts.basicFoundation;
baseLaunchCost = ItemStack.with();
startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500);
conditionWave = 3;
launchPeriod = 2;
requirements = with(stainedMountains, 40);
blockRequirements = new Block[]{Blocks.thermalGenerator};
resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand};
}};
impact0078 = new SectorPreset("impact0078"){{
loadout = Loadouts.basicNucleus;
baseLaunchCost = ItemStack.list();
startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500);
conditionWave = 3;
launchPeriod = 2;
//requirements = with(nuclearComplex, 40);
//blockRequirements = new Block[]{Blocks.thermalGenerator};
//resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium};
}};*/
}
}

View File

@@ -9,7 +9,7 @@ import mindustry.type.StatusEffect;
import static mindustry.Vars.*;
public class StatusEffects implements ContentList{
public static StatusEffect none, burning, freezing, wet, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss;
public static StatusEffect none, burning, freezing, wet, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed;
@Override
public void load(){
@@ -17,7 +17,7 @@ public class StatusEffects implements ContentList{
none = new StatusEffect("none");
burning = new StatusEffect("burning"){{
damage = 0.08f; //over 10 seconds, this would be 48 damage
damage = 0.12f; //over 8 seconds, this would be 60 damage
effect = Fx.burning;
init(() -> {
@@ -47,8 +47,9 @@ public class StatusEffects implements ContentList{
wet = new StatusEffect("wet"){{
color = Color.royal;
speedMultiplier = 0.9f;
speedMultiplier = 0.94f;
effect = Fx.wet;
effectChance = 0.09f;
init(() -> {
trans(shocked, ((unit, time, newTime, result) -> {
@@ -81,6 +82,12 @@ public class StatusEffects implements ContentList{
effectChance = 0.1f;
}};
sporeSlowed = new StatusEffect("spore-slowed"){{
speedMultiplier = 0.8f;
effect = Fx.sapped;
effectChance = 0.04f;
}};
tarred = new StatusEffect("tarred"){{
speedMultiplier = 0.6f;
effect = Fx.oily;

View File

@@ -11,6 +11,8 @@ import mindustry.type.*;
import mindustry.world.*;
import static mindustry.content.Blocks.*;
import static mindustry.content.SectorPresets.*;
import static mindustry.content.SectorPresets.craters;
import static mindustry.content.UnitTypes.*;
import static mindustry.type.ItemStack.*;
@@ -110,7 +112,7 @@ public class TechTree implements ContentList{
});
node(Items.coal, with(Items.lead, 3000), () -> {
node(Items.graphite, with(Items.coal, 3000), () -> {
node(Items.graphite, with(Items.coal, 1000), () -> {
node(graphitePress, () -> {
node(Items.titanium, with(Items.graphite, 6000, Items.copper, 10000, Items.lead, 10000), () -> {
node(pneumaticDrill, () -> {
@@ -405,17 +407,87 @@ public class TechTree implements ContentList{
});
//TODO research sectors
/*
node(SectorPresets.groundZero, () -> {
node(SectorPresets.nuclearComplex, () -> {
node(SectorPresets.craters, () -> {
node(SectorPresets.saltFlats, () -> {
node(groundZero, () -> {
node(frozenForest, Seq.with(
new SectorComplete(groundZero),
new Research(junction),
new Research(router)
), () -> {
node(craters, Seq.with(
new SectorComplete(frozenForest),
new Research(mender),
new Research(combustionGenerator)
), () -> {
node(ruinousShores, Seq.with(
new SectorComplete(craters),
new Research(graphitePress),
new Research(combustionGenerator),
new Research(kiln),
new Research(mechanicalPump)
), () -> {
node(tarFields, Seq.with(
new SectorComplete(ruinousShores),
new Research(coalCentrifuge),
new Research(conduit),
new Research(wave)
), () -> {
node(desolateRift, Seq.with(
new SectorComplete(tarFields),
new Research(thermalGenerator),
new Research(thoriumReactor)
), () -> {
});
});
node(saltFlats, Seq.with(
new SectorComplete(ruinousShores),
new Research(groundFactory),
new Research(airFactory),
new Research(door),
new Research(waterExtractor)
), () -> {
});
});
node(overgrowth, Seq.with(
new SectorComplete(craters),
new SectorComplete(fungalPass),
new Research(cultivator),
new Research(sporePress),
new Research(UnitTypes.mace),
new Research(UnitTypes.flare)
), () -> {
});
});
node(stainedMountains, Seq.with(
new SectorComplete(frozenForest),
new Research(pneumaticDrill),
new Research(powerNode),
new Research(turbineGenerator)
), () -> {
node(fungalPass, Seq.with(
new SectorComplete(stainedMountains),
new Research(groundFactory),
new Research(door),
new Research(siliconSmelter)
), () -> {
node(nuclearComplex, Seq.with(
new SectorComplete(fungalPass),
new Research(thermalGenerator),
new Research(laserDrill)
), () -> {
});
});
});
});
});
*/
});
}
@@ -448,6 +520,12 @@ public class TechTree implements ContentList{
return new TechNode(content, requirements, children);
}
private static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
TechNode node = new TechNode(content, empty, children);
node.objectives = objectives;
return node;
}
private static TechNode node(UnlockableContent block){
return node(block, () -> {});
}

View File

@@ -42,7 +42,7 @@ public class UnitTypes implements ContentList{
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class, Payloadc.class}) UnitType mega;
//air + building + mining
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class, Trailc.class}) UnitType alpha, beta, gamma;
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class}) UnitType alpha, beta, gamma;
//water
public static @EntityDef({Unitc.class, WaterMovec.class, Commanderc.class}) UnitType risso, minke, bryde;
@@ -193,7 +193,7 @@ public class UnitTypes implements ContentList{
bullet = new LightningBulletType(){{
lightningColor = hitColor = Pal.heal;
damage = 11f;
damage = 12f;
lightningLength = 7;
lightningLengthRand = 7;
shootEffect = Fx.shootHeal;
@@ -263,7 +263,7 @@ public class UnitTypes implements ContentList{
speed = 1f;
splashDamageRadius = 55f;
instantDisappear = true;
splashDamage = 40f;
splashDamage = 45f;
killShooter = true;
hittable = false;
collidesAir = true;
@@ -527,10 +527,10 @@ public class UnitTypes implements ContentList{
}};
antumbra = new UnitType("antumbra"){{
speed = 1.25f;
speed = 1.13f;
accel = 0.035f;
drag = 0.05f;
rotateSpeed = 3.5f;
rotateSpeed = 1.9f;
flying = true;
lowAltitude = true;
health = 9000;
@@ -539,20 +539,70 @@ public class UnitTypes implements ContentList{
engineSize = 5.3f;
hitsize = 58f;
weapons.add(new Weapon(){{
y = 1.5f;
reload = 28f;
BulletType missiles = new MissileBulletType(2.7f, 10){{
width = 8f;
height = 8f;
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 40f;
splashDamage = 40f;
ammoMultiplier = 4f;
lifetime = 80f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
status = StatusEffects.blasted;
statusDuration = 60f;
}};
weapons.add(
new Weapon("missiles-mount"){{
y = 8f;
x = 17f;
reload = 20f;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
rotateSpeed = 8f;
bullet = missiles;
shootSound = Sounds.shoot;
}});
rotate = true;
occlusion = 6f;
}},
new Weapon("missiles-mount"){{
y = -8f;
x = 17f;
reload = 35;
rotateSpeed = 8f;
ejectEffect = Fx.shellEjectSmall;
bullet = missiles;
shootSound = Sounds.shoot;
rotate = true;
occlusion = 6f;
}},
new Weapon("large-bullet-mount"){{
y = 2f;
x = 10f;
shootY = 12f;
reload = 10;
shake = 1f;
rotateSpeed = 2f;
ejectEffect = Fx.shellEjectSmall;
shootSound = Sounds.shootBig;
rotate = true;
occlusion = 8f;
bullet = new BasicBulletType(7f, 60){{
width = 12f;
height = 18f;
shootEffect = Fx.shootBig;
}};
}}
);
}};
eclipse = new UnitType("eclipse"){{
speed = 1.1f;
speed = 1.09f;
accel = 0.02f;
drag = 0.05f;
rotateSpeed = 2.5f;
rotateSpeed = 1f;
flying = true;
lowAltitude = true;
health = 18000;
@@ -562,12 +612,74 @@ public class UnitTypes implements ContentList{
destructibleWreck = false;
armor = 13f;
weapons.add(new Weapon(){{
y = 1.5f;
reload = 28f;
weapons.add(
new Weapon("large-laser-mount"){{
shake = 4f;
shootY = 9f;
x = 18f;
y = 5f;
rotateSpeed = 2f;
reload = 50f;
recoil = 4f;
shootSound = Sounds.laser;
occlusion = 20f;
rotate = true;
bullet = new LaserBulletType(){{
damage = 75f;
sideAngle = 20f;
sideWidth = 1.5f;
sideLength = 80f;
width = 25f;
length = 200f;
shootEffect = Fx.shockwave;
colors = new Color[]{Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
}};
}},
new Weapon("missiles-mount"){{
x = 11f;
y = 27f;
rotateSpeed = 2f;
reload = 4f;
shootSound = Sounds.flame;
occlusion = 7f;
rotate = true;
recoil = 0.5f;
bullet = Bullets.pyraFlame;
}},
new Weapon("large-artillery"){{
y = -13f;
x = 20f;
reload = 18f;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
rotateSpeed = 7f;
shake = 1f;
shootSound = Sounds.shoot;
rotate = true;
occlusion = 12f;
bullet = new ArtilleryBulletType(3.2f, 12){{
trailMult = 0.8f;
hitEffect = Fx.massiveExplosion;
knockback = 1.5f;
lifetime = 140f;
height = 12f;
width = 12f;
collidesTiles = false;
ammoMultiplier = 4f;
splashDamageRadius = 60f;
splashDamage = 60f;
backColor = Pal.missileYellowBack;
frontColor = Pal.missileYellow;
trailEffect = Fx.artilleryTrail;
trailSize = 6f;
hitShake = 4f;
shootEffect = Fx.shootBig2;
status = StatusEffects.blasted;
statusDuration = 60f;
}};
}});
}};
@@ -575,13 +687,17 @@ public class UnitTypes implements ContentList{
//region air support
mono = new UnitType("mono"){{
defaultController = MinerAI::new;
flying = true;
drag = 0.05f;
accel = 0.15f;
speed = 2f;
drag = 0.06f;
accel = 0.12f;
speed = 1.1f;
health = 100;
engineSize = 1.8f;
engineOffset = 5.7f;
itemCapacity = 30;
range = 50f;
mineTier = 1;
mineSpeed = 2.5f;
@@ -592,7 +708,7 @@ public class UnitTypes implements ContentList{
flying = true;
drag = 0.05f;
speed = 2f;
speed = 1.9f;
rotateSpeed = 15f;
accel = 0.1f;
range = 70f;
@@ -611,7 +727,7 @@ public class UnitTypes implements ContentList{
weapons.add(new Weapon("heal-weapon-mount"){{
y = -2.5f;
x = 3.5f;
reload = 34f;
reload = 30f;
ejectEffect = Fx.none;
recoil = 2f;
shootSound = Sounds.pew;
@@ -620,11 +736,11 @@ public class UnitTypes implements ContentList{
inaccuracy = 15f;
alternate = true;
bullet = new MissileBulletType(4f, 10){{
bullet = new MissileBulletType(4f, 12){{
homingPower = 0.08f;
weaveMag = 4;
weaveScale = 4;
lifetime = 50f;
lifetime = 56f;
keepVelocity = false;
shootEffect = Fx.shootHeal;
smokeEffect = Fx.hitLaser;
@@ -792,7 +908,6 @@ public class UnitTypes implements ContentList{
shots = 1;
inaccuracy = 3f;
ejectEffect = Fx.shellEjectBig;
bullet = new ArtilleryBulletType(3.2f, 12){{
@@ -859,7 +974,6 @@ public class UnitTypes implements ContentList{
//region core
alpha = new UnitType("alpha"){{
//TODO maybe these should be changed
defaultController = BuilderAI::new;
isCounted = false;
@@ -872,12 +986,12 @@ public class UnitTypes implements ContentList{
rotateSpeed = 15f;
accel = 0.1f;
itemCapacity = 30;
health = 80f;
health = 120f;
engineOffset = 6f;
hitsize = 8f;
weapons.add(new Weapon("small-basic-weapon"){{
reload = 20f;
reload = 17f;
x = 2.75f;
y = 1f;
@@ -887,13 +1001,12 @@ public class UnitTypes implements ContentList{
lifetime = 60f;
shootEffect = Fx.shootSmall;
smokeEffect = Fx.shootSmallSmoke;
tileDamageMultiplier = 0.1f;
tileDamageMultiplier = 0.95f;
}};
}});
}};
beta = new UnitType("beta"){{
//TODO maybe these should be changed
defaultController = BuilderAI::new;
isCounted = false;
@@ -906,7 +1019,7 @@ public class UnitTypes implements ContentList{
rotateSpeed = 17f;
accel = 0.1f;
itemCapacity = 50;
health = 120f;
health = 150f;
engineOffset = 6f;
hitsize = 9f;
rotateShooting = false;
@@ -933,7 +1046,6 @@ public class UnitTypes implements ContentList{
}};
gamma = new UnitType("gamma"){{
//TODO maybe these should be changed
defaultController = BuilderAI::new;
isCounted = false;
@@ -946,7 +1058,7 @@ public class UnitTypes implements ContentList{
rotateSpeed = 19f;
accel = 0.11f;
itemCapacity = 70;
health = 160f;
health = 190f;
engineOffset = 6f;
hitsize = 10f;

View File

@@ -70,7 +70,6 @@ public class Weathers implements ContentList{
}
};
//TODO should apply wet effect
rain = new Weather("rain"){
float yspeed = 5f, xspeed = 1.5f, padding = 16f, size = 40f, density = 1200f;
TextureRegion[] splashes = new TextureRegion[12];
@@ -78,6 +77,7 @@ public class Weathers implements ContentList{
{
attrs.set(Attribute.light, -0.2f);
attrs.set(Attribute.water, 0.2f);
status = StatusEffects.wet;
}
@Override
@@ -255,6 +255,8 @@ public class Weathers implements ContentList{
{
attrs.set(Attribute.spores, 0.5f);
attrs.set(Attribute.light, -0.1f);
status = StatusEffects.sporeSlowed;
statusGround = false;
}
@Override

View File

@@ -94,7 +94,6 @@ public class Control implements ApplicationListener, Loadable{
tutorial.reset();
hiscore = false;
saves.resetSave();
});
@@ -109,20 +108,24 @@ public class Control implements ApplicationListener, Loadable{
Events.on(GameOverEvent.class, event -> {
state.stats.wavesLasted = state.wave;
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
Effect.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
//the restart dialog can show info for any number of scenarios
Call.gameOver(event.winner);
});
//add player when world loads regardless
Events.on(WorldLoadEvent.class, e -> {
player.add();
});
//autohost for pvp maps
Events.on(WorldLoadEvent.class, event -> app.post(() -> {
player.add();
if(state.rules.pvp && !net.active()){
try{
net.host(port);
player.admin(true);
}catch(IOException e){
ui.showException("$server.error", e);
ui.showException("@server.error", e);
state.set(State.menu);
}
}
@@ -171,7 +174,7 @@ public class Control implements ApplicationListener, Loadable{
}
});
Events.on(Trigger.newGame, () -> {
Events.run(Trigger.newGame, () -> {
Building core = player.closestCore();
if(core == null) return;
@@ -188,7 +191,7 @@ public class Control implements ApplicationListener, Loadable{
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block()));
Time.run(Fx.coreLand.lifetime, () -> {
Fx.launch.at(core);
Effects.shake(5f, 5f, core);
Effect.shake(5f, 5f, core);
});
});
@@ -257,7 +260,7 @@ public class Control implements ApplicationListener, Loadable{
}
//TODO move
public void handleLaunch(CoreEntity tile){
public void handleLaunch(CoreBuild tile){
LaunchCorec ent = LaunchCore.create();
ent.set(tile);
ent.block(Blocks.coreShard);
@@ -312,7 +315,7 @@ public class Control implements ApplicationListener, Loadable{
}catch(SaveException e){
Log.err(e);
sector.save = null;
Time.runTask(10f, () -> ui.showErrorMessage("$save.corrupted"));
Time.runTask(10f, () -> ui.showErrorMessage("@save.corrupted"));
slot.delete();
playSector(origin, sector);
}
@@ -434,6 +437,7 @@ public class Control implements ApplicationListener, Loadable{
ui.showStartupInfo("[accent]v6[] is currently in [accent]pre-alpha[].\n" +
"[lightgray]This means:[]\n" +
"- Content is missing\n" +
"- [scarlet]Mobile[] is not supported.\n" +
"- Most [scarlet]Unit AI[] does not work\n" +
"- Many units are [scarlet]missing[] or unfinished\n" +
"- The campaign is completely unfinished\n" +
@@ -450,7 +454,7 @@ public class Control implements ApplicationListener, Loadable{
//display UI scale changed dialog
if(Core.settings.getBool("uiscalechanged", false)){
Core.app.post(() -> Core.app.post(() -> {
BaseDialog dialog = new BaseDialog("$confirm");
BaseDialog dialog = new BaseDialog("@confirm");
dialog.setFillParent(true);
float[] countdown = {60 * 11};
@@ -469,9 +473,9 @@ public class Control implements ApplicationListener, Loadable{
}).pad(10f).expand().center();
dialog.buttons.defaults().size(200f, 60f);
dialog.buttons.button("$uiscale.cancel", exit);
dialog.buttons.button("@uiscale.cancel", exit);
dialog.buttons.button("$ok", () -> {
dialog.buttons.button("@ok", () -> {
Core.settings.put("uiscalechanged", false);
dialog.hide();
});

View File

@@ -2,7 +2,6 @@ package mindustry.core;
import arc.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
@@ -88,27 +87,35 @@ public class Logic implements ApplicationListener{
//when loading a 'damaged' sector, propagate the damage
Events.on(WorldLoadEvent.class, e -> {
if(state.isCampaign() && state.rules.sector.getSecondsPassed() > 0 && state.rules.sector.hasBase()){
if(state.isCampaign()){
long seconds = state.rules.sector.getSecondsPassed();
CoreEntity core = state.rules.defaultTeam.core();
//update correct storage capacity
state.rules.sector.save.meta.secinfo.storageCapacity = core.storageCapacity;
CoreBuild core = state.rules.defaultTeam.core();
//apply fractional damage based on how many turns have passed for this sector
float turnsPassed = seconds / (turnDuration / 60f);
if(state.rules.sector.hasWaves()){
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){
//add new items recieved
state.rules.sector.calculateRecievedItems().each((item, amount) -> core.items.add(item, amount));
//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.setReceivedItems(new Seq<>());
state.rules.sector.setExtraItems(new ItemSeq());
//validation
for(Item item : content.items()){
//ensure positive items
if(core.items.get(item) < 0) core.items.set(item, 0);
//cap the items
if(core.items.get(item) > core.storageCapacity) core.items.set(item, core.storageCapacity);
}
}
state.rules.sector.setSecondsPassed(0);
@@ -262,15 +269,15 @@ public class Logic implements ApplicationListener{
Time.runTask(30f, () -> {
Sector origin = sector.save.meta.secinfo.origin;
if(origin != null){
Seq<ItemStack> stacks = origin.getReceivedItems();
ItemSeq stacks = origin.getExtraItems();
//add up all items into list
for(Building entity : state.teams.playerCores()){
entity.items.each((item, amount) -> ItemStack.insert(stacks, item, amount));
entity.items.each(stacks::add);
}
//save received items
origin.setReceivedItems(stacks);
origin.setExtraItems(stacks);
}
//remove all the cores
@@ -324,7 +331,9 @@ public class Logic implements ApplicationListener{
}
if(!state.isPaused()){
state.secinfo.update();
if(state.isCampaign()){
state.secinfo.update();
}
if(state.isCampaign()){
universe.update();

View File

@@ -2,6 +2,7 @@ package mindustry.core;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
@@ -11,6 +12,7 @@ import arc.util.serialization.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -63,7 +65,7 @@ public class NetClient implements ApplicationListener{
reset();
ui.loadfrag.hide();
ui.loadfrag.show("$connecting.data");
ui.loadfrag.show("@connecting.data");
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
@@ -80,7 +82,7 @@ public class NetClient implements ApplicationListener{
c.uuid = platform.getUUID();
if(c.uuid == null){
ui.showErrorMessage("$invalidid");
ui.showErrorMessage("@invalidid");
ui.loadfrag.hide();
disconnectQuietly();
return;
@@ -104,14 +106,14 @@ public class NetClient implements ApplicationListener{
if(packet.reason != null){
if(packet.reason.equals("closed")){
ui.showSmall("$disconnect", "$disconnect.closed");
ui.showSmall("@disconnect", "@disconnect.closed");
}else if(packet.reason.equals("timeout")){
ui.showSmall("$disconnect", "$disconnect.timeout");
ui.showSmall("@disconnect", "@disconnect.timeout");
}else if(packet.reason.equals("error")){
ui.showSmall("$disconnect", "$disconnect.error");
ui.showSmall("@disconnect", "@disconnect.error");
}
}else{
ui.showErrorMessage("$disconnect");
ui.showErrorMessage("@disconnect");
}
});
@@ -261,7 +263,7 @@ public class NetClient implements ApplicationListener{
if(reason.extraText() != null){
ui.showText(reason.toString(), reason.extraText());
}else{
ui.showText("$disconnect", reason.toString());
ui.showText("@disconnect", reason.toString());
}
}
ui.loadfrag.hide();
@@ -271,7 +273,7 @@ public class NetClient implements ApplicationListener{
public static void kick(String reason){
netClient.disconnectQuietly();
logic.reset();
ui.showText("$disconnect", reason, Align.left);
ui.showText("@disconnect", reason, Align.left);
ui.loadfrag.hide();
}
@@ -314,7 +316,6 @@ public class NetClient implements ApplicationListener{
ui.showLabel(message, duration, worldx, worldy);
}
/*
@Remote(variants = Variant.both, unreliable = true)
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
if(effect == null) return;
@@ -325,7 +326,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.both)
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
onEffect(effect, x, y, rotation, color);
}*/
}
@Remote(variants = Variant.both)
public static void infoToast(String message, float duration){
@@ -344,10 +345,11 @@ public class NetClient implements ApplicationListener{
Groups.clear();
netClient.removed.clear();
logic.reset();
netClient.connecting = true;
net.setClientLoaded(false);
ui.loadfrag.show("$connecting.data");
ui.loadfrag.show("@connecting.data");
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
@@ -481,7 +483,7 @@ public class NetClient implements ApplicationListener{
Log.err("Failed to load data!");
ui.loadfrag.hide();
quiet = true;
ui.showErrorMessage("$disconnect.data");
ui.showErrorMessage("@disconnect.data");
net.disconnect();
timeoutTime = 0f;
}
@@ -552,7 +554,7 @@ public class NetClient implements ApplicationListener{
void sync(){
if(timer.get(0, playerSyncTime)){
BuildPlan[] requests = null;
if(player.isBuilder() && control.input.isBuilding){
if(player.isBuilder()){
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.builder().plans().size, 10);
@@ -564,7 +566,7 @@ public class NetClient implements ApplicationListener{
Unit unit = player.dead() ? Nulls.unit : player.unit();
Call.clientShapshot(lastSent++,
Call.clientSnapshot(lastSent++,
player.dead(),
unit.x, unit.y,
player.unit().aimX(), player.unit().aimY(),
@@ -572,7 +574,7 @@ public class NetClient implements ApplicationListener{
unit instanceof Mechc ? ((Mechc)unit).baseRotation() : 0,
unit.vel.x, unit.vel.y,
player.miner().mineTile(),
player.boosting, player.shooting, ui.chatfrag.shown(),
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);

View File

@@ -6,8 +6,8 @@ import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.CommandHandler.*;
import arc.util.io.*;
import arc.util.serialization.*;
@@ -332,6 +332,8 @@ public class NetServer implements ApplicationListener{
Call.sendMessage(Strings.format("[lightgray]A player has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
target.name, votes, votesRequired()));
checkPass();
}
boolean checkPass(){
@@ -526,7 +528,7 @@ public class NetServer implements ApplicationListener{
}
@Remote(targets = Loc.client, unreliable = true)
public static void clientShapshot(
public static void clientSnapshot(
Player player,
int snapshotID,
boolean dead,
@@ -535,7 +537,7 @@ public class NetServer implements ApplicationListener{
float rotation, float baseRotation,
float xVelocity, float yVelocity,
Tile mining,
boolean boosting, boolean shooting, boolean chatting,
boolean boosting, boolean shooting, boolean chatting, boolean building,
@Nullable BuildPlan[] requests,
float viewX, float viewY, float viewWidth, float viewHeight
){
@@ -556,6 +558,10 @@ public class NetServer implements ApplicationListener{
shooting = false;
}
if(!player.dead() && (player.unit().type().flying || !player.unit().type().canBoost)){
boosting = false;
}
//TODO these need to be assigned elsewhere
player.mouseX = pointerX;
player.mouseY = pointerY;
@@ -568,6 +574,7 @@ public class NetServer implements ApplicationListener{
if(player.isBuilder()){
player.builder().clearBuilding();
player.builder().updateBuilding(building);
}
if(player.isMiner()){
@@ -607,7 +614,10 @@ public class NetServer implements ApplicationListener{
unit.vel.set(xVelocity, yVelocity).limit(unit.type().speed);
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
float maxSpeed = player.unit().type().speed;
float maxSpeed = (boosting ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
if(unit.isGrounded()){
maxSpeed *= unit.floorSpeedMultiplier();
}
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
if(con.lastUnit != unit){
@@ -687,6 +697,7 @@ public class NetServer implements ApplicationListener{
logic.skipWave();
}else if(action == AdminAction.ban){
netServer.admins.banPlayerIP(other.con.address);
netServer.admins.banPlayerID(other.con.uuid);
other.kick(KickReason.banned);
Log.info("&lc@ has banned @.", player.name, other.name);
}else if(action == AdminAction.kick){
@@ -705,13 +716,15 @@ public class NetServer implements ApplicationListener{
@Remote(targets = Loc.client)
public static void connectConfirm(Player player){
player.add();
if(player.con == null || player.con.hasConnected) return;
player.add();
player.con.hasConnected = true;
if(Config.showConnectMessages.bool()){
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
Log.info("&lm[@] &y@ has connected. ", player.uuid(), player.name);
Log.info("&lm[@] &y@ has connected.", player.uuid(), player.name);
}
if(!Config.motd.string().equalsIgnoreCase("off")){
@@ -739,7 +752,7 @@ public class NetServer implements ApplicationListener{
if(!headless && !closing && net.server() && state.isMenu()){
closing = true;
ui.loadfrag.show("$server.closing");
ui.loadfrag.show("@server.closing");
Time.runTask(5f, () -> {
net.closeServer();
ui.loadfrag.hide();
@@ -777,7 +790,7 @@ public class NetServer implements ApplicationListener{
syncStream.reset();
short sent = 0;
for(Building entity : Groups.tile){
for(Building entity : Groups.build){
if(!entity.block().sync) continue;
sent ++;
@@ -802,11 +815,11 @@ public class NetServer implements ApplicationListener{
public void writeEntitySnapshot(Player player) throws IOException{
syncStream.reset();
Seq<CoreEntity> cores = state.teams.cores(player.team());
Seq<CoreBuild> cores = state.teams.cores(player.team());
dataStream.writeByte(cores.size);
for(CoreEntity entity : cores){
for(CoreBuild entity : cores){
dataStream.writeInt(entity.tile().pos());
entity.items.write(Writes.get(dataStream));
}

View File

@@ -111,7 +111,7 @@ public interface Platform{
* @param extension File extension to filter
*/
default void showFileChooser(boolean open, String extension, Cons<Fi> cons){
new FileChooser(open ? "$open" : "$save", file -> file.extEquals(extension), open, file -> {
new FileChooser(open ? "@open" : "@save", file -> file.extEquals(extension), open, file -> {
if(!open){
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{
@@ -129,7 +129,7 @@ public interface Platform{
if(mobile){
showFileChooser(true, extensions[0], cons);
}else{
new FileChooser("$open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
new FileChooser("@open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
}
}

View File

@@ -87,6 +87,10 @@ public class Renderer implements ApplicationListener{
}
}
public boolean isLanding(){
return landTime > 0;
}
public float weatherAlpha(){
return weatherAlpha;
}
@@ -134,7 +138,7 @@ public class Renderer implements ApplicationListener{
}catch(Throwable e){
e.printStackTrace();
settings.put("bloom", false);
ui.showErrorMessage("$error.bloom");
ui.showErrorMessage("@error.bloom");
}
}
@@ -314,7 +318,7 @@ public class Renderer implements ApplicationListener{
int memory = w * h * 4 / 1024 / 1024;
if(memory >= 65){
ui.showInfo("$screenshot.invalid");
ui.showInfo("@screenshot.invalid");
return;
}

View File

@@ -24,6 +24,7 @@ import mindustry.editor.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.LogicDialog;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.ui.fragments.*;
@@ -67,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
public SchematicsDialog schematics;
public ModsDialog mods;
public ColorPicker picker;
public LogicDialog logic;
public Cursor drillCursor, unloadCursor;
@@ -85,7 +87,7 @@ public class UI implements ApplicationListener, Loadable{
Fonts.def.getData().markupEnabled = true;
Fonts.def.setOwnsTexture(false);
Core.assets.getAll(BitmapFont.class, new Seq<>()).each(font -> font.setUseIntegerPositions(true));
Core.assets.getAll(Font.class, new Seq<>()).each(font -> font.setUseIntegerPositions(true));
Core.scene = new Scene();
Core.input.addProcessor(Core.scene);
@@ -99,6 +101,7 @@ public class UI implements ApplicationListener, Loadable{
Dialog.setHideAction(() -> sequence(fadeOut(0.1f)));
Tooltips.getInstance().animations = false;
Tooltips.getInstance().textProvider = text -> new Tooltip(t -> t.background(Styles.black5).margin(4f).add(text));
Core.settings.setErrorHandler(e -> {
e.printStackTrace();
@@ -118,7 +121,7 @@ public class UI implements ApplicationListener, Loadable{
@Override
public Seq<AssetDescriptor> getDependencies(){
return Seq.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", BitmapFont.class), new AssetDescriptor<>("default", BitmapFont.class), new AssetDescriptor<>("chat", BitmapFont.class));
return Seq.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", Font.class), new AssetDescriptor<>("default", Font.class), new AssetDescriptor<>("chat", Font.class));
}
@Override
@@ -178,6 +181,7 @@ public class UI implements ApplicationListener, Loadable{
research = new ResearchDialog();
mods = new ModsDialog();
schematics = new SchematicsDialog();
logic = new LogicDialog();
Group group = Core.scene.root;
@@ -224,7 +228,7 @@ public class UI implements ApplicationListener, Loadable{
}
public void loadAnd(Runnable call){
loadAnd("$loading", call);
loadAnd("@loading", call);
}
public void loadAnd(String text, Runnable call){
@@ -238,7 +242,7 @@ public class UI implements ApplicationListener, Loadable{
public void showTextInput(String titleText, String dtext, int textLength, String def, boolean inumeric, Cons<String> confirmed){
if(mobile){
Core.input.getTextInput(new TextInput(){{
this.title = (titleText.startsWith("$") ? Core.bundle.get(titleText.substring(1)) : titleText);
this.title = (titleText.startsWith("@") ? Core.bundle.get(titleText.substring(1)) : titleText);
this.text = def;
this.numeric = inumeric;
this.maxLength = textLength;
@@ -251,11 +255,11 @@ public class UI implements ApplicationListener, Loadable{
TextField field = cont.field(def, t -> {}).size(330f, 50f).get();
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
buttons.defaults().size(120, 54).pad(4);
buttons.button("$ok", () -> {
buttons.button("@ok", () -> {
confirmed.get(field.getText());
hide();
}).disabled(b -> field.getText().isEmpty());
buttons.button("$cancel", this::hide);
buttons.button("@cancel", this::hide);
keyDown(KeyCode.enter, () -> {
String text = field.getText();
if(!text.isEmpty()){
@@ -282,6 +286,7 @@ public class UI implements ApplicationListener, Loadable{
public void showInfoFade(String info){
Table table = new Table();
table.touchable = Touchable.disabled;
table.setFillParent(true);
table.actions(Actions.fadeOut(7f, Interp.fade), Actions.remove());
table.top().add(info).style(Styles.outlineLabel).padTop(10);
@@ -340,7 +345,7 @@ public class UI implements ApplicationListener, Loadable{
new Dialog(""){{
getCell(cont).growX();
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center);
buttons.button("$ok", () -> {
buttons.button("@ok", () -> {
hide();
listener.run();
}).size(110, 50).pad(4);
@@ -351,7 +356,7 @@ public class UI implements ApplicationListener, Loadable{
new Dialog(""){{
getCell(cont).growX();
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.left);
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
@@ -359,13 +364,13 @@ public class UI implements ApplicationListener, Loadable{
new Dialog(""){{
setFillParent(true);
cont.margin(15f);
cont.add("$error.title");
cont.add("@error.title");
cont.row();
cont.image().width(300f).pad(2).height(4f).color(Color.scarlet);
cont.row();
cont.add(text).pad(2f).growX().wrap().get().setAlignment(Align.center);
cont.row();
cont.button("$ok", this::hide).size(120, 50).pad(4);
cont.button("@ok", this::hide).size(120, 50).pad(4);
}}.show();
}
@@ -380,17 +385,17 @@ public class UI implements ApplicationListener, Loadable{
setFillParent(true);
cont.margin(15);
cont.add("$error.title").colspan(2);
cont.add("@error.title").colspan(2);
cont.row();
cont.image().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
cont.row();
cont.add((text.startsWith("$") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
cont.add((text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
cont.row();
Collapser col = new Collapser(base -> base.pane(t -> t.margin(14f).add(Strings.neatError(exc)).color(Color.lightGray).left()), true);
cont.button("$details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
cont.button("$ok", this::hide).size(110, 50).fillX().left();
cont.button("@details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
cont.button("@ok", this::hide).size(110, 50).fillX().left();
cont.row();
cont.add(col).colspan(2).pad(2);
}}.show();
@@ -407,14 +412,14 @@ public class UI implements ApplicationListener, Loadable{
cont.row();
cont.add(text).width(400f).wrap().get().setAlignment(align, align);
cont.row();
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
public void showInfoText(String titleText, String text){
new Dialog(titleText){{
cont.margin(15).add(text).width(400f).wrap().left().get().setAlignment(Align.left, Align.left);
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
@@ -423,7 +428,7 @@ public class UI implements ApplicationListener, Loadable{
cont.margin(10).add(text);
titleTable.row();
titleTable.image().color(Pal.accent).height(3f).growX().pad(2f);
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
@@ -436,8 +441,8 @@ public class UI implements ApplicationListener, Loadable{
dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.button("$cancel", dialog::hide);
dialog.buttons.button("$ok", () -> {
dialog.buttons.button("@cancel", dialog::hide);
dialog.buttons.button("@ok", () -> {
dialog.hide();
confirmed.run();
});
@@ -477,10 +482,11 @@ public class UI implements ApplicationListener, Loadable{
public void announce(String text){
Table t = new Table();
t.touchable = Touchable.disabled;
t.background(Styles.black3).margin(8f)
.add(text).style(Styles.outlineLabel);
t.update(() -> t.setPosition(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, Align.center));
t.actions(Actions.fadeOut(3, Interp.pow4In));
t.actions(Actions.fadeOut(3, Interp.pow4In), Actions.remove());
Core.scene.add(t);
}
@@ -489,7 +495,7 @@ public class UI implements ApplicationListener, Loadable{
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.button("$ok", () -> {
dialog.buttons.button("@ok", () -> {
dialog.hide();
confirmed.run();
});

View File

@@ -277,7 +277,7 @@ public class World{
}catch(Throwable e){
Log.err(e);
if(!headless){
ui.showErrorMessage("$map.invalid");
ui.showErrorMessage("@map.invalid");
Core.app.post(() -> state.set(State.menu));
invalidMap = true;
}
@@ -291,17 +291,17 @@ public class World{
if(!headless){
if(state.teams.playerCores().size == 0 && !checkRules.pvp){
ui.showErrorMessage("$map.nospawn");
ui.showErrorMessage("@map.nospawn");
invalidMap = true;
}else if(checkRules.pvp){ //pvp maps need two cores to be valid
if(state.teams.getActive().count(TeamData::hasCore) < 2){
invalidMap = true;
ui.showErrorMessage("$map.nospawn.pvp");
ui.showErrorMessage("@map.nospawn.pvp");
}
}else if(checkRules.attackMode){ //attack maps need two cores to be valid
invalidMap = state.teams.get(state.rules.waveTeam).noCores();
if(invalidMap){
ui.showErrorMessage("$map.nospawn.attack");
ui.showErrorMessage("@map.nospawn.attack");
}
}
}else{

View File

@@ -137,14 +137,11 @@ public class MapEditor{
if(isFloor){
tile.setFloor(drawBlock.asFloor());
}else{
tile.setBlock(drawBlock);
if(drawBlock.synthetic()){
tile.setTeam(drawTeam);
}
if(drawBlock.rotate && tile.build != null && tile.build.rotation != rotation){
addTileOp(TileOp.get(tile.x, tile.y, (byte)OpType.rotation.ordinal(), (byte)rotation));
tile.build.rotation = (byte)rotation;
}
tile.setBlock(drawBlock, drawTeam, rotation);
}
};

View File

@@ -58,7 +58,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
infoDialog = new MapInfoDialog(editor);
generateDialog = new MapGenerateDialog(editor, true);
menu = new BaseDialog("$menu");
menu = new BaseDialog("@menu");
menu.addCloseButton();
float swidth = 180f;
@@ -66,41 +66,41 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu.cont.table(t -> {
t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5);
t.button("$editor.savemap", Icon.save, this::save);
t.button("@editor.savemap", Icon.save, this::save);
t.button("$editor.mapinfo", Icon.pencil, () -> {
t.button("@editor.mapinfo", Icon.pencil, () -> {
infoDialog.show();
menu.hide();
});
t.row();
t.button("$editor.generate", Icon.terrain, () -> {
t.button("@editor.generate", Icon.terrain, () -> {
generateDialog.show(generateDialog::applyToEditor);
menu.hide();
});
t.button("$editor.resize", Icon.resize, () -> {
t.button("@editor.resize", Icon.resize, () -> {
resizeDialog.show();
menu.hide();
});
t.row();
t.button("$editor.import", Icon.download, () -> createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
t.button("@editor.import", Icon.download, () -> createDialog("@editor.import",
"@editor.importmap", "@editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
"@editor.importfile", "@editor.importfile.description", Icon.file, (Runnable)() ->
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
ui.showInfo("@editor.errorimage");
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
});
})),
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
"@editor.importimage", "@editor.importimage.description", Icon.fileImage, (Runnable)() ->
platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> {
try{
@@ -108,16 +108,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showException("$editor.errorload", e);
ui.showException("@editor.errorload", e);
Log.err(e);
}
})))
);
t.button("$editor.export", Icon.upload, () -> createDialog("$editor.export",
"$editor.exportfile", "$editor.exportfile.description", Icon.file,
t.button("@editor.export", Icon.upload, () -> createDialog("@editor.export",
"@editor.exportfile", "@editor.exportfile.description", Icon.file,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
"$editor.exportimage", "$editor.exportimage.description", Icon.fileImage,
"@editor.exportimage", "@editor.exportimage.description", Icon.fileImage,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), "png", file -> {
Pixmap out = MapIO.writeImage(editor.tiles());
file.writePNG(out);
@@ -128,7 +128,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu.cont.row();
if(steam){
menu.cont.button("$editor.publish.workshop", Icon.link, () -> {
menu.cont.button("@editor.publish.workshop", Icon.link, () -> {
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){
@@ -146,26 +146,26 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(map == null) return;
if(map.tags.get("description", "").length() < 4){
ui.showErrorMessage("$editor.nodescription");
ui.showErrorMessage("@editor.nodescription");
return;
}
if(!Structs.contains(Gamemode.all, g -> g.valid(map))){
ui.showErrorMessage("$map.nospawn");
ui.showErrorMessage("@map.nospawn");
return;
}
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "@workshop.listing" : "@view.workshop" : "@editor.publish.workshop"));
menu.cont.row();
}
menu.cont.button("$editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.button("@editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.row();
menu.cont.button("$quit", Icon.exit, () -> {
menu.cont.button("@quit", Icon.exit, () -> {
tryExit();
menu.hide();
}).size(swidth * 2f + 10, 60f);
@@ -182,7 +182,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
try{
editor.beginEdit(map);
}catch(Exception e){
ui.showException("$editor.errorload", e);
ui.showException("@editor.errorload", e);
Log.err(e);
}
}));
@@ -280,14 +280,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(name.isEmpty()){
infoDialog.show();
Core.app.post(() -> ui.showErrorMessage("$editor.save.noname"));
Core.app.post(() -> ui.showErrorMessage("@editor.save.noname"));
}else{
Map map = maps.all().find(m -> m.name().equals(name));
if(map != null && !map.custom){
handleSaveBuiltin(map);
}else{
returned = maps.saveMap(editor.getTags());
ui.showInfoFade("$editor.saved");
ui.showInfoFade("@editor.saved");
}
}
@@ -299,7 +299,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
/** Called when a built-in map save is attempted.*/
protected void handleSaveBuiltin(Map map){
ui.showErrorMessage("$editor.save.overwrite");
ui.showErrorMessage("@editor.save.overwrite");
}
/**
@@ -368,7 +368,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
show();
}catch(Exception e){
Log.err(e);
ui.showException("$editor.errorload", e);
ui.showException("@editor.errorload", e);
}
});
}
@@ -521,7 +521,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
tools.row();
tools.table(Tex.underline, t -> t.add("$editor.teams"))
tools.table(Tex.underline, t -> t.add("@editor.teams"))
.colspan(3).height(40).width(size * 3f + 3f).padBottom(3);
tools.row();
@@ -557,7 +557,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
t.top();
t.add("$editor.brush");
t.add("@editor.brush");
t.row();
t.add(slider).width(size * 3f - 20).padTop(4f);
}).padTop(5).growX().top();
@@ -649,11 +649,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
private void tryExit(){
if(!saved){
ui.showConfirm("$confirm", "$editor.unsaved", this::hide);
}else{
hide();
}
ui.showConfirm("@confirm", "@editor.unsaved", this::hide);
}
private void addBlockSelection(Table table){

View File

@@ -58,34 +58,34 @@ public class MapGenerateDialog extends BaseDialog{
/** @param applied whether or not to use the applied in-game mode. */
public MapGenerateDialog(MapEditor editor, boolean applied){
super("$editor.generate");
super("@editor.generate");
this.editor = editor;
this.applied = applied;
shown(this::setup);
addCloseButton();
if(applied){
buttons.button("$editor.apply", () -> {
buttons.button("@editor.apply", () -> {
ui.loadAnd(() -> {
apply();
hide();
});
}).size(160f, 64f);
}else{
buttons.button("$settings.reset", () -> {
buttons.button("@settings.reset", () -> {
filters.set(maps.readFilters(""));
rebuildFilters();
update();
}).size(160f, 64f);
}
buttons.button("$editor.randomize", () -> {
buttons.button("@editor.randomize", () -> {
for(GenerateFilter filter : filters){
filter.randomize();
}
update();
}).size(160f, 64f);
buttons.button("$add", Icon.add, this::showAdd).height(64f).width(140f);
buttons.button("@add", Icon.add, this::showAdd).height(64f).width(140f);
if(!applied){
hidden(this::apply);
@@ -221,41 +221,45 @@ public class MapGenerateDialog extends BaseDialog{
for(GenerateFilter filter : filters){
//main container
filterTable.table(Tex.button, c -> {
filterTable.table(Tex.pane, c -> {
c.margin(0);
//icons to perform actions
c.table(t -> {
t.top();
t.add(filter.name()).padTop(5).color(Pal.accent).growX().left();
c.table(Tex.whiteui, t -> {
t.setColor(Pal.gray);
t.row();
t.top().left();
t.add(filter.name()).left().padLeft(6);
t.table(b -> {
ImageButtonStyle style = Styles.cleari;
b.defaults().size(50f);
b.button(Icon.refresh, style, () -> {
filter.randomize();
update();
});
t.add().growX();
b.button(Icon.upOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.max(0, idx - 1));
rebuildFilters();
update();
});
b.button(Icon.downOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
rebuildFilters();
update();
});
b.button(Icon.trash, style, () -> {
filters.remove(filter);
rebuildFilters();
update();
});
ImageButtonStyle style = Styles.geni;
t.defaults().size(42f);
t.button(Icon.refresh, style, () -> {
filter.randomize();
update();
});
}).fillX();
t.button(Icon.upOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.max(0, idx - 1));
rebuildFilters();
update();
});
t.button(Icon.downOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
rebuildFilters();
update();
});
t.button(Icon.cancel, style, () -> {
filters.remove(filter);
rebuildFilters();
update();
});
}).growX();
c.row();
//all the options
c.table(f -> {
@@ -269,7 +273,7 @@ public class MapGenerateDialog extends BaseDialog{
}).growX().left();
f.row();
}
}).grow().left().pad(2).top();
}).grow().left().pad(6).top();
}).width(280f).pad(3).top().left().fillY();
if(++i % cols == 0){
filterTable.row();
@@ -277,12 +281,12 @@ public class MapGenerateDialog extends BaseDialog{
}
if(filters.isEmpty()){
filterTable.add("$filters.empty").wrap().width(200f);
filterTable.add("@filters.empty").wrap().width(200f);
}
}
void showAdd(){
BaseDialog selection = new BaseDialog("$add");
BaseDialog selection = new BaseDialog("@add");
selection.setFillParent(false);
selection.cont.defaults().size(210f, 60f);
int i = 0;
@@ -300,7 +304,7 @@ public class MapGenerateDialog extends BaseDialog{
if(++i % 2 == 0) selection.cont.row();
}
selection.cont.button("$filter.defaultores", () -> {
selection.cont.button("@filter.defaultores", () -> {
maps.addDefaultOres(filters);
rebuildFilters();
update();

View File

@@ -16,7 +16,7 @@ public class MapInfoDialog extends BaseDialog{
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
public MapInfoDialog(MapEditor editor){
super("$editor.mapinfo");
super("@editor.mapinfo");
this.editor = editor;
this.waveInfo = new WaveInfoDialog(editor);
this.generate = new MapGenerateDialog(editor, false);
@@ -32,47 +32,47 @@ public class MapInfoDialog extends BaseDialog{
ObjectMap<String, String> tags = editor.getTags();
cont.pane(t -> {
t.add("$editor.mapname").padRight(8).left();
t.add("@editor.mapname").padRight(8).left();
t.defaults().padTop(15);
TextField name = t.field(tags.get("name", ""), text -> {
tags.put("name", text);
}).size(400, 55f).addInputDialog(50).get();
name.setMessageText("$unknown");
name.setMessageText("@unknown");
t.row();
t.add("$editor.description").padRight(8).left();
t.add("@editor.description").padRight(8).left();
TextArea description = t.area(tags.get("description", ""), Styles.areaField, text -> {
tags.put("description", text);
}).size(400f, 140f).addInputDialog(1000).get();
t.row();
t.add("$editor.author").padRight(8).left();
t.add("@editor.author").padRight(8).left();
TextField author = t.field(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> {
tags.put("author", text);
Core.settings.put("mapAuthor", text);
}).size(400, 55f).addInputDialog(50).get();
author.setMessageText("$unknown");
author.setMessageText("@unknown");
t.row();
t.add("$editor.rules").padRight(8).left();
t.button("$edit", () -> {
t.add("@editor.rules").padRight(8).left();
t.button("@edit", () -> {
ruleInfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules());
hide();
}).left().width(200f);
t.row();
t.add("$editor.waves").padRight(8).left();
t.button("$edit", () -> {
t.add("@editor.waves").padRight(8).left();
t.button("@edit", () -> {
waveInfo.show();
hide();
}).left().width(200f);
t.row();
t.add("$editor.generation").padRight(8).left();
t.button("$edit", () -> {
t.add("@editor.generation").padRight(8).left();
t.button("@edit", () -> {
generate.show(Vars.maps.readFilters(editor.getTags().get("genfilters", "")),
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)));
hide();

View File

@@ -14,11 +14,11 @@ public class MapLoadDialog extends BaseDialog{
private Map selected = null;
public MapLoadDialog(Cons<Map> loader){
super("$editor.loadmap");
super("@editor.loadmap");
shown(this::rebuild);
TextButton button = new TextButton("$load");
TextButton button = new TextButton("@load");
button.setDisabled(() -> selected == null);
button.clicked(() -> {
if(selected != null){
@@ -28,7 +28,7 @@ public class MapLoadDialog extends BaseDialog{
});
buttons.defaults().size(200f, 50f);
buttons.button("$cancel", this::hide);
buttons.button("@cancel", this::hide);
buttons.add(button);
}
@@ -64,9 +64,9 @@ public class MapLoadDialog extends BaseDialog{
}
if(maps.all().size == 0){
table.add("$maps.none").center();
table.add("@maps.none").center();
}else{
cont.add("$editor.loadmap");
cont.add("@editor.loadmap");
}
cont.row();

View File

@@ -12,7 +12,7 @@ public class MapResizeDialog extends BaseDialog{
int width, height;
public MapResizeDialog(MapEditor editor, Intc2 cons){
super("$editor.resizemap");
super("@editor.resizemap");
shown(() -> {
cont.clear();
width = editor.width();
@@ -21,7 +21,7 @@ public class MapResizeDialog extends BaseDialog{
Table table = new Table();
for(boolean w : Mathf.booleans){
table.add(w ? "$width" : "$height").padRight(8f);
table.add(w ? "@width" : "@height").padRight(8f);
table.defaults().height(60f).padTop(8);
table.field((w ? width : height) + "", TextFieldFilter.digitsOnly, value -> {
@@ -37,8 +37,8 @@ public class MapResizeDialog extends BaseDialog{
});
buttons.defaults().size(200f, 50f);
buttons.button("$cancel", this::hide);
buttons.button("$ok", () -> {
buttons.button("@cancel", this::hide);
buttons.button("@ok", () -> {
cons.get(width, height);
hide();
});

View File

@@ -213,9 +213,9 @@ public class MapView extends Element implements GestureListener{
y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.height();
if(editor.drawBlock.size % 2 == 0 && tool != EditorTool.eraser){
return Tmp.g1.set((int)(x - 0.5f), (int)(y - 0.5f));
return Tmp.p1.set((int)(x - 0.5f), (int)(y - 0.5f));
}else{
return Tmp.g1.set((int)x, (int)y);
return Tmp.p1.set((int)x, (int)y);
}
}

View File

@@ -35,7 +35,7 @@ public class WaveGraph extends Table{
Lines.precise(true);
GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
BitmapFont font = Fonts.outline;
Font font = Fonts.outline;
lay.setText(font, "1");
@@ -137,7 +137,7 @@ public class WaveGraph extends Table{
ButtonGroup<Button> group = new ButtonGroup<>();
for(Mode m : Mode.all){
t.button("$wavemode." + m.name(), Styles.fullTogglet, () -> {
t.button("@wavemode." + m.name(), Styles.fullTogglet, () -> {
mode = m;
}).group(group).height(35f).update(b -> b.setChecked(m == mode)).width(130f);
}
@@ -173,22 +173,25 @@ public class WaveGraph extends Table{
colors.clear();
colors.left();
for(UnitType type : used){
colors.button(b -> {
Color tcolor = color(type).cpy();
b.image().size(32f).update(i -> i.setColor(b.isChecked() ? Tmp.c1.set(tcolor).mul(0.5f) : tcolor)).get().act(1);
b.image(type.icon(Cicon.medium)).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
b.margin(0f);
}, Styles.fullTogglet, () -> {
if(!hidden.add(type)){
hidden.remove(type);
}
colors.pane(t -> {
t.left();
for(UnitType type : used){
t.button(b -> {
Color tcolor = color(type).cpy();
b.image().size(32f).update(i -> i.setColor(b.isChecked() ? Tmp.c1.set(tcolor).mul(0.5f) : tcolor)).get().act(1);
b.image(type.icon(Cicon.medium)).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
b.margin(0f);
}, Styles.fullTogglet, () -> {
if(!hidden.add(type)){
hidden.remove(type);
}
used.clear();
used.addAll(usedCopy);
for(UnitType o : hidden) used.remove(o);
}).update(b -> b.setChecked(hidden.contains(type)));
}
used.clear();
used.addAll(usedCopy);
for(UnitType o : hidden) used.remove(o);
}).update(b -> b.setChecked(hidden.contains(type)));
}
}).get().setScrollingDisabled(false, true);
for(UnitType type : hidden){
used.remove(type);

View File

@@ -32,7 +32,7 @@ public class WaveInfoDialog extends BaseDialog{
private WaveGraph graph = new WaveGraph();
public WaveInfoDialog(MapEditor editor){
super("$waves.title");
super("@waves.title");
shown(this::setup);
hidden(() -> {
@@ -48,29 +48,29 @@ public class WaveInfoDialog extends BaseDialog{
onResize(this::setup);
addCloseButton();
buttons.button("$waves.edit", () -> {
BaseDialog dialog = new BaseDialog("$waves.edit");
buttons.button("@waves.edit", () -> {
BaseDialog dialog = new BaseDialog("@waves.edit");
dialog.addCloseButton();
dialog.setFillParent(false);
dialog.cont.defaults().size(210f, 64f);
dialog.cont.button("$waves.copy", () -> {
ui.showInfoFade("$waves.copied");
dialog.cont.button("@waves.copy", () -> {
ui.showInfoFade("@waves.copied");
Core.app.setClipboardText(maps.writeWaves(groups));
dialog.hide();
}).disabled(b -> groups == null);
dialog.cont.row();
dialog.cont.button("$waves.load", () -> {
dialog.cont.button("@waves.load", () -> {
try{
groups = maps.readWaves(Core.app.getClipboardText());
buildGroups();
}catch(Exception e){
e.printStackTrace();
ui.showErrorMessage("$waves.invalid");
ui.showErrorMessage("@waves.invalid");
}
dialog.hide();
}).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty());
dialog.cont.row();
dialog.cont.button("$settings.reset", () -> ui.showConfirm("$confirm", "$settings.clear.confirm", () -> {
dialog.cont.button("@settings.reset", () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
groups = JsonIO.copy(defaultWaves.get());
buildGroups();
dialog.hide();
@@ -130,12 +130,12 @@ public class WaveInfoDialog extends BaseDialog{
cont.stack(new Table(Tex.clear, main -> {
main.pane(t -> table = t).growX().growY().padRight(8f).get().setScrollingDisabled(true, false);
main.row();
main.button("$add", () -> {
main.button("@add", () -> {
if(groups == null) groups = new Seq<>();
groups.add(new SpawnGroup(lastType));
buildGroups();
}).growX().height(70f);
}), new Label("$waves.none"){{
}), new Label("@waves.none"){{
visible(() -> groups.isEmpty());
this.touchable = Touchable.disabled;
setWrap(true);
@@ -180,7 +180,7 @@ public class WaveInfoDialog extends BaseDialog{
updateWaves();
}
}).width(100f);
spawns.add("$waves.to").padLeft(4).padRight(4);
spawns.add("@waves.to").padLeft(4).padRight(4);
spawns.field(group.end == never ? "" : (group.end + 1) + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
group.end = Strings.parseInt(text) - 1;
@@ -193,14 +193,14 @@ public class WaveInfoDialog extends BaseDialog{
});
t.row();
t.table(p -> {
p.add("$waves.every").padRight(4);
p.add("@waves.every").padRight(4);
p.field(group.spacing + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text) && Strings.parseInt(text) > 0){
group.spacing = Strings.parseInt(text);
updateWaves();
}
}).width(100f);
p.add("$waves.waves").padLeft(4);
p.add("@waves.waves").padLeft(4);
});
t.row();
@@ -219,7 +219,7 @@ public class WaveInfoDialog extends BaseDialog{
updateWaves();
}
}).width(80f);
a.add("$waves.perspawn").padLeft(4);
a.add("@waves.perspawn").padLeft(4);
});
t.row();
t.table(a -> {
@@ -237,17 +237,17 @@ public class WaveInfoDialog extends BaseDialog{
updateWaves();
}
}).width(80f);
a.add("$waves.shields").padLeft(4);
a.add("@waves.shields").padLeft(4);
});
t.row();
t.check("$waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
t.check("@waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
}).width(340f).pad(8);
table.row();
}
}else{
table.add("$editor.default");
table.add("@editor.default");
}
updateWaves();

View File

@@ -59,7 +59,7 @@ public class Damage{
}
float shake = Math.min(explosiveness / 4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effect.shake(shake, shake, x, y);
Fx.dynamicExplosion.at(x, y, radius / 8f);
}

View File

@@ -1,15 +1,26 @@
package mindustry.entities;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Effect{
private static final float shakeFalloff = 10000f;
private static final EffectContainer container = new EffectContainer();
private static int lastid = 0;
private static final Seq<Effect> all = new Seq<>();
public final int id;
public final Cons<EffectContainer> renderer;
@@ -21,10 +32,11 @@ public class Effect{
public float groundDuration;
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
this.id = lastid++;
this.id = all.size;
this.lifetime = life;
this.renderer = renderer;
this.size = clipsize;
all.add(this);
}
public Effect(float life, Cons<EffectContainer> renderer){
@@ -43,35 +55,35 @@ public class Effect{
}
public void at(Position pos){
Effects.create(this, pos.getX(), pos.getY(), 0, Color.white, null);
create(this, pos.getX(), pos.getY(), 0, Color.white, null);
}
public void at(Position pos, float rotation){
Effects.create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
}
public void at(float x, float y){
Effects.create(this, x, y, 0, Color.white, null);
create(this, x, y, 0, Color.white, null);
}
public void at(float x, float y, float rotation){
Effects.create(this, x, y, rotation, Color.white, null);
create(this, x, y, rotation, Color.white, null);
}
public void at(float x, float y, float rotation, Color color){
Effects.create(this, x, y, rotation, color, null);
create(this, x, y, rotation, color, null);
}
public void at(float x, float y, Color color){
Effects.create(this, x, y, 0, color, null);
create(this, x, y, 0, color, null);
}
public void at(float x, float y, float rotation, Color color, Object data){
Effects.create(this, x, y, rotation, color, data);
create(this, x, y, rotation, color, data);
}
public void at(float x, float y, float rotation, Object data){
Effects.create(this, x, y, rotation, Color.white, data);
create(this, x, y, rotation, Color.white, data);
}
public void render(int id, Color color, float life, float rotation, float x, float y, Object data){
@@ -81,6 +93,84 @@ public class Effect{
Draw.reset();
}
public static @Nullable Effect get(int id){
return id >= all.size || id < 0 ? null : all.get(id);
}
private static void shake(float intensity, float duration){
if(!headless){
Vars.renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
if(Core.camera == null) return;
float distance = Core.camera.position.dst(x, y);
if(distance < 1) distance = 1;
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
}
public static void shake(float intensity, float duration, Position loc){
shake(intensity, duration, loc.getX(), loc.getY());
}
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
if(headless || effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = Core.camera.bounds(Tmp.r1);
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
if(view.overlaps(pos)){
EffectState entity = EffectState.create();
entity.effect(effect);
entity.rotation(rotation);
entity.data(data);
entity.lifetime(effect.lifetime);
entity.set(x, y);
entity.color().set(color);
if(data instanceof Posc) entity.parent((Posc)data);
entity.add();
}
}
}
public static void decal(TextureRegion region, float x, float y, float rotation){
decal(region, x, y, rotation, 3600f, Pal.rubble);
}
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
if(headless || region == null || !Core.atlas.isFound(region)) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
Decal decal = Decal.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public static void scorch(float x, float y, int size){
if(headless) return;
size = Mathf.clamp(size, 0, 9);
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static void rubble(float x, float y, int blockSize){
if(headless) return;
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static class EffectContainer implements Scaled{
public float x, y, time, lifetime, rotation;
public Color color;

View File

@@ -1,92 +0,0 @@
package mindustry.entities;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Effects{
private static final float shakeFalloff = 10000f;
private static void shake(float intensity, float duration){
if(!headless){
renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
if(Core.camera == null) return;
float distance = Core.camera.position.dst(x, y);
if(distance < 1) distance = 1;
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
}
public static void shake(float intensity, float duration, Position loc){
shake(intensity, duration, loc.getX(), loc.getY());
}
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
if(headless || effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = Core.camera.bounds(Tmp.r1);
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
if(view.overlaps(pos)){
EffectState entity = EffectState.create();
entity.effect(effect);
entity.rotation(rotation);
entity.data(data);
entity.lifetime(effect.lifetime);
entity.set(x, y);
entity.color().set(color);
if(data instanceof Posc) entity.parent((Posc)data);
entity.add();
}
}
}
public static void decal(TextureRegion region, float x, float y, float rotation){
decal(region, x, y, rotation, 3600f, Pal.rubble);
}
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
if(headless || region == null || !Core.atlas.isFound(region)) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
Decal decal = Decal.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public static void scorch(float x, float y, int size){
if(headless) return;
size = Mathf.clamp(size, 0, 9);
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static void rubble(float x, float y, int blockSize){
if(headless) return;
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
}

View File

@@ -8,7 +8,7 @@ class GroupDefs<G>{
@GroupDef(value = Playerc.class, mapping = true) G player;
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;
@GroupDef(value = Buildingc.class) G tile;
@GroupDef(value = Buildingc.class) G build;
@GroupDef(value = Syncc.class, mapping = true) G sync;
@GroupDef(value = Drawc.class) G draw;
@GroupDef(value = WeatherStatec.class) G weather;

View File

@@ -83,7 +83,7 @@ public class Puddles{
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
Fires.create(tile);
if(Mathf.chance(0.006 * amount)){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
}
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot Puddle
if(Mathf.chance(0.5f * amount)){

View File

@@ -36,7 +36,7 @@ public class Units{
public static int getCap(Team team){
//wave team has no cap
if((team == state.rules.waveTeam && state.rules.waves) || (state.isCampaign() && team == state.rules.waveTeam)){
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam)){
return Integer.MAX_VALUE;
}
return state.rules.unitCap + indexer.getExtraUnits(team);

View File

@@ -1,5 +1,6 @@
package mindustry.entities.abilities;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
@@ -70,8 +71,18 @@ public class ForceFieldAbility implements Ability{
if(unit.shield > 0){
Draw.z(Layer.shields);
Draw.color(unit.team.color, Color.white, Mathf.clamp(unit.shieldAlpha));
Fill.poly(unit.x, unit.y, 6, realRad);
if(Core.settings.getBool("animatedshields")){
Fill.poly(unit.x, unit.y, 6, realRad);
}else{
Lines.stroke(1.5f);
Draw.alpha(0.09f);
Fill.poly(unit.x, unit.y, 6, radius);
Draw.alpha(1f);
Lines.poly(unit.x, unit.y, 6, radius);
}
}
}

View File

@@ -54,7 +54,7 @@ public abstract class BulletType extends Content{
/** Status effect applied on hit. */
public StatusEffect status = StatusEffects.none;
/** Intensity of applied status effect in terms of duration. */
public float statusDuration = 60 * 10f;
public float statusDuration = 60 * 8f;
/** Whether this bullet type collides with tiles. */
public boolean collidesTiles = true;
/** Whether this bullet type collides with tiles that are of the same team. */
@@ -142,7 +142,7 @@ public abstract class BulletType extends Content{
hitEffect.at(x, y, b.rotation(), hitColor);
hitSound.at(b);
Effects.shake(hitShake, hitShake, b);
Effect.shake(hitShake, hitShake, b);
if(fragBullet != null){
for(int i = 0; i < fragBullets; i++){
@@ -270,7 +270,7 @@ public abstract class BulletType extends Content{
}
public void createNet(Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
Call.createBullet(this, team, x, y, damage, angle, velocityScl, lifetimeScl);
Call.createBullet(this, team, x, y, angle, damage, velocityScl, lifetimeScl);
}
@Remote(called = Loc.server, unreliable = true)

View File

@@ -53,7 +53,7 @@ public class ContinuousLaserBulletType extends BulletType{
}
if(shake > 0){
Effects.shake(shake, shake, b);
Effect.shake(shake, shake, b);
}
}
@@ -67,7 +67,7 @@ public class ContinuousLaserBulletType extends BulletType{
for(int i = 0; i < tscales.length; i++){
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * 35f);
Lines.stroke((9f + Mathf.absin(Time.time(), 0.8f, 1.5f)) * b.fout() * strokes[s] * tscales[i]);
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], CapStyle.none);
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], false);
}
}

View File

@@ -78,7 +78,7 @@ public class LaserBulletType extends BulletType{
for(Color color : colors){
Draw.color(color);
Lines.stroke((cwidth *= lengthFalloff) * b.fout());
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, CapStyle.none);
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, false);
Tmp.v1.trns(b.rotation(), baseLen);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, Lines.getStroke() * 1.22f, cwidth * 2f + width / 2f, b.rotation());

View File

@@ -25,7 +25,7 @@ public class LiquidBulletType extends BulletType{
}
lifetime = 74f;
statusDuration = 90f;
statusDuration = 60f * 2f;
despawnEffect = Fx.none;
hitEffect = Fx.hitLiquid;
smokeEffect = Fx.none;
@@ -69,7 +69,7 @@ public class LiquidBulletType extends BulletType{
super.despawned(b);
//don't create liquids when the projectile despawns
hitEffect.at(b.x, b.y, liquid.color);
hitEffect.at(b.x, b.y, b.rotation(), liquid.color);
}
@Override

View File

@@ -15,6 +15,7 @@ public class MissileBulletType extends BasicBulletType{
height = 8f;
hitSound = Sounds.explosion;
trailChance = 0.2f;
lifetime = 49f;
}
public MissileBulletType(float speed, float damage){

View File

@@ -29,13 +29,7 @@ abstract class BuilderComp implements Unitc{
@Import float x, y, rotation;
@SyncLocal Queue<BuildPlan> plans = new Queue<>();
transient boolean updateBuilding = true;
@Override
public void controller(UnitController next){
//reset building state so AI controlled units will always start off building
updateBuilding = true;
}
@SyncLocal transient boolean updateBuilding = true;
@Override
public void update(){
@@ -76,7 +70,7 @@ abstract class BuilderComp implements Unitc{
Tile tile = world.tile(current.x, current.y);
if(!within(tile, finalPlaceDst)){
if(within(tile, finalPlaceDst)){
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
}
@@ -85,12 +79,12 @@ abstract class BuilderComp implements Unitc{
boolean hasAll = infinite || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
if(hasAll){
Build.beginPlace(current.block, team(), current.x, current.y, current.rotation);
Call.beginPlace(current.block, team(), current.x, current.y, current.rotation);
}else{
current.stuck = true;
}
}else if(!current.initialized && current.breaking && Build.validBreak(team(), current.x, current.y)){
Build.beginBreak(team(), current.x, current.y);
Call.beginBreak(team(), current.x, current.y);
}else{
plans.removeFirst();
return;
@@ -200,6 +194,10 @@ abstract class BuilderComp implements Unitc{
}
}
boolean activelyBuilding(){
return isBuilding() && updateBuilding;
}
/** Return the build request currently active, or the one at the top of the queue.*/
@Nullable
BuildPlan buildPlan(){

View File

@@ -18,11 +18,13 @@ import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.audio.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -37,7 +39,7 @@ import static mindustry.Vars.*;
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
@Component(base = true)
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable{
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Senseable, Controllable{
//region vars and initialization
static final float timeToSleep = 60f * 1;
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
@@ -54,6 +56,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient boolean updateFlow;
transient byte dump;
transient int rotation;
transient boolean enabled = true;
PowerModule power;
ItemModule items;
@@ -97,7 +100,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
}
health(block.health);
health = block.health;
maxHealth(block.health);
timer(new Interval(block.timers));
@@ -141,7 +144,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public final void readBase(Reads read){
health = read.f();
rotation(read.b());
rotation = read.b();
team = Team.get(read.b());
if(items != null) items.read(read);
if(power != null) power.read(read);
@@ -294,6 +297,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Base efficiency. If this entity has non-buffered power, returns the power %, otherwise returns 1. */
public float efficiency(){
//disabled -> 0 efficiency
if(!enabled) return 0;
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
@@ -325,6 +330,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region handler methods
/** This is for logic blocks. */
public void handleString(Object value){
}
public void created(){}
public boolean shouldConsume(){
@@ -715,11 +725,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void drawStatus(){
if(block.consumes.any()){
if(block.enableDrawStatus && block.consumes.any()){
float brcx = tile.drawx() + (block.size * tilesize / 2f) - (tilesize / 2f);
float brcy = tile.drawy() - (block.size * tilesize / 2f) + (tilesize / 2f);
Draw.z(Layer.blockOver);
Draw.z(Layer.power + 1);
Draw.color(Pal.gray);
Fill.square(brcx, brcy, 2.5f, 45);
Draw.color(cons.status().color);
@@ -833,8 +843,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
/** Called when the block is tapped.*/
public void tapped(Player player){
/** Called when the block is tapped by the local player. */
public void tapped(){
}
@@ -879,7 +889,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame);
if(!floor().solid && !floor().isLiquid){
Effects.rubble(x, y, block.size);
Effect.rubble(x, y, block.size);
}
}
@@ -1060,6 +1070,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return true;
}
public boolean canPickup(){
return true;
}
public void removeFromProximity(){
onProximityRemoved();
tmpTiles.clear();
@@ -1157,6 +1171,41 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.x) return x;
if(sensor == LAccess.y) return y;
if(sensor == LAccess.team) return team.id;
if(sensor == LAccess.health) return health;
if(sensor == LAccess.efficiency) return efficiency();
if(sensor == LAccess.rotation) return rotation;
if(sensor == LAccess.totalItems && items != null) return items.total();
if(sensor == LAccess.totalLiquids && liquids != null) return liquids.total();
if(sensor == LAccess.totalPower && power != null && block.consumes.hasPower()) return power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
if(sensor == LAccess.itemCapacity) return block.itemCapacity;
if(sensor == LAccess.liquidCapacity) return block.liquidCapacity;
if(sensor == LAccess.powerCapacity) return block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
if(sensor == LAccess.powerNetIn && power != null) return power.graph.getPowerProduced();
if(sensor == LAccess.powerNetOut && power != null) return power.graph.getPowerNeeded();
if(sensor == LAccess.powerNetStored && power != null) return power.graph.getLastPowerStored();
if(sensor == LAccess.powerNetCapacity && power != null) return power.graph.getBatteryCapacity();
return 0;
}
@Override
public double sense(Content content){
if(content instanceof Item && items != null) return items.get((Item)content);
if(content instanceof Liquid && liquids != null) return liquids.get((Liquid)content);
return 0;
}
@Override
public void control(LAccess type, double p1, double p2, double p3, double p4){
if(type == LAccess.enabled){
enabled = !Mathf.zero((float)p1);
}
}
@Override
public void remove(){
if(sound != null){
@@ -1189,7 +1238,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
loops.play(block.idleSound, base(), block.idleSoundVolume);
}
updateTile();
if(enabled || !block.noUpdateDisabled){
updateTile();
}
if(items != null){
items.update(updateFlow);

View File

@@ -17,6 +17,8 @@ abstract class CommanderComp implements Unitc{
transient @Nullable Formation formation;
transient Seq<Unit> controlling = new Seq<>();
/** minimum speed of any unit in the formation. */
transient float minFormationSpeed;
@Override
public void update(){
@@ -45,18 +47,26 @@ abstract class CommanderComp implements Unitc{
void command(Formation formation, Seq<Unit> units){
clearCommand();
float spacing = hitSize() * 1.7f;
minFormationSpeed = type().speed;
controlling.addAll(units);
for(Unit unit : units){
unit.controller(new FormationAI(base(), formation));
FormationAI ai;
unit.controller(ai = new FormationAI(base(), formation));
spacing = Math.max(spacing, ai.formationSize());
minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
}
this.formation = formation;
//update formation spacing based on max size
formation.pattern.spacing = spacing;
members.clear();
for(Unitc u : units){
members.add((FormationAI)u.controller());
}
//TODO doesn't handle units that don't fit a formation
formation.addMembers(members);
}

View File

@@ -3,7 +3,7 @@ package mindustry.entities.comp;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import static mindustry.Vars.collisions;
import static mindustry.Vars.*;
@Component
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{

View File

@@ -6,7 +6,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class HealthComp implements Entityc{
abstract class HealthComp implements Entityc, Posc{
static final float hitDuration = 9f;
float health;

View File

@@ -11,7 +11,7 @@ import mindustry.gen.*;
abstract class HitboxComp implements Posc, QuadTreeObject{
@Import float x, y;
transient float lastX, lastY, hitSize;
transient float lastX, lastY, deltaX, deltaY, hitSize;
@Override
public void update(){
@@ -33,6 +33,8 @@ abstract class HitboxComp implements Posc, QuadTreeObject{
}
void updateLastPosition(){
deltaX = x - lastX;
deltaY = y - lastY;
lastX = x;
lastY = y;
}
@@ -41,20 +43,12 @@ abstract class HitboxComp implements Posc, QuadTreeObject{
}
float deltaX(){
return x - lastX;
}
float deltaY(){
return y - lastY;
}
float deltaLen(){
return Mathf.len(deltaX(), deltaY());
return Mathf.len(deltaX, deltaY);
}
float deltaAngle(){
return Mathf.angle(deltaX(), deltaY());
return Mathf.angle(deltaX, deltaY);
}
boolean collides(Hitboxc other){

View File

@@ -100,7 +100,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
//shake when legs contact ground
if(type.landShake > 0){
Effects.shake(type.landShake, type.landShake, l.base);
Effect.shake(type.landShake, type.landShake, l.base);
}
if(type.legSplashDamage > 0){

View File

@@ -40,19 +40,19 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
public void update(){
Building core = closestCore();
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && within(core, mineTransferRange)){
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && within(core, mineTransferRange) && !offloadImmediately()){
int accepted = core.acceptStack(item(), stack().amount, this);
if(accepted > 0){
Call.transferItemTo(item(), accepted,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
mineTile.worldy() + Mathf.range(tilesize / 2f), core);
clearItem();
}
}
if(mineTile == null || core == null || mineTile.block() != Blocks.air || dst(mineTile.worldx(), mineTile.worldy()) > miningRange
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).isBuilding())
|| mineTile.drop() == null || !acceptsItem(mineTile.drop()) || !canMine(mineTile.drop())){
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).activelyBuilding())
|| mineTile.drop() == null || !canMine(mineTile.drop())){
mineTile = null;
mineTimer = 0f;
}else{
@@ -60,25 +60,30 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
rotation(Mathf.slerpDelta(rotation(), angleTo(mineTile.worldx(), mineTile.worldy()), 0.4f));
mineTimer += Time.delta *type.mineSpeed;
if(Mathf.chance(0.06 * Time.delta)){
Fx.pulverizeSmall.at(mineTile.worldx() + Mathf.range(tilesize / 2f), mineTile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color);
}
if(mineTimer >= 50f + item.hardness*10f){
mineTimer = 0;
if(within(core, mineTransferRange) && core.acceptStack(item, 1, this) == 1 && offloadImmediately()){
Call.transferItemTo(item, 1,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
mineTile.worldy() + Mathf.range(tilesize / 2f), core);
}else if(acceptsItem(item)){
//this is clientside, since items are synced anyway
InputHandler.transferItemToUnit(item,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f),
this);
}else{
mineTile = null;
mineTimer = 0f;
}
}
if(Mathf.chance(0.06 * Time.delta)){
Fx.pulverizeSmall.at(mineTile.worldx() + Mathf.range(tilesize / 2f), mineTile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color);
}
}
}

View File

@@ -6,6 +6,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.payloads.*;
@@ -69,17 +70,22 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
boolean dropUnit(UnitPayload payload){
Unit u = payload.unit;
Fx.unitDrop.at(this);
//can't drop ground units
if((tileOn() == null || tileOn().solid()) && u.elevation < 0.1f){
if(((tileOn() == null || tileOn().solid()) && u.elevation < 0.1f) || (!floorOn().isLiquid && u instanceof WaterMovec)){
return false;
}
//clients do not drop payloads
if(Vars.net.client()) return true;
u.set(this);
u.trns(Tmp.v1.rnd(Mathf.random(2f)));
u.rotation(rotation);
//reset the ID to a new value to make sure it's synced
u.id = EntityGroup.nextId();
u.add();
Fx.unitDrop.at(u);
return true;
}

View File

@@ -54,11 +54,13 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
return unit instanceof Minerc;
}
public @Nullable CoreEntity closestCore(){
public @Nullable
CoreBuild closestCore(){
return state.teams.closestCore(x, y, team);
}
public @Nullable CoreEntity core(){
public @Nullable
CoreBuild core(){
return team.core();
}
@@ -104,7 +106,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
clearUnit();
}
CoreEntity core = closestCore();
CoreBuild core = closestCore();
if(!dead()){
set(unit);
@@ -114,7 +116,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
//update some basic state to sync things
if(unit.type().canBoost){
Tile tile = unit.tileOn();
unit.elevation(Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f));
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f);
}
}else if(core != null){
//have a small delay before death to prevent the camera from jumping around too quickly
@@ -205,7 +207,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
Draw.z(Layer.playerName);
float z = Drawf.text();
BitmapFont font = Fonts.def;
Font font = Fonts.def;
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
final float nameHeight = 11;
final float textHeight = 15;

View File

@@ -9,12 +9,14 @@ import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -23,7 +25,7 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component(base = true)
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable{
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Senseable{
@Import boolean hovering;
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health;
@@ -50,15 +52,43 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
lookAt(x, y);
}
public boolean inRange(Position other){
return within(other, type.range);
}
public boolean hasWeapons(){
return type.hasWeapons();
}
public float range(){
return type.range;
}
@Replace
public float clipSize(){
return type.region.getWidth() * 2f;
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.totalItems) return stack().amount;
if(sensor == LAccess.rotation) return rotation;
if(sensor == LAccess.health) return health;
if(sensor == LAccess.x) return x;
if(sensor == LAccess.y) return y;
if(sensor == LAccess.team) return team.id;
if(sensor == LAccess.shooting) return isShooting() ? 1 : 0;
if(sensor == LAccess.shootX) return aimX();
if(sensor == LAccess.shootY) return aimY();
return 0;
}
@Override
public double sense(Content content){
if(content == stack().item) return stack().amount;
return 0;
}
@Override
public int itemCapacity(){
return type.itemCapacity;
@@ -130,7 +160,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
this.type = type;
this.maxHealth = type.health;
this.drag = type.drag;
this.elevation = type.flying ? 1f : 0;
this.armor = type.armor;
this.hitSize = type.hitsize;
this.hovering = type.hovering;
@@ -174,7 +203,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void landed(){
if(type.landShake > 0f){
Effects.shake(type.landShake, type.landShake, this);
Effect.shake(type.landShake, type.landShake, this);
}
type.landed(base());
@@ -245,6 +274,14 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(floor.damageTaken > 0f){
damageContinuous(floor.damageTaken);
}
if(!net.client() && tile.solid()){
if(type.canBoost){
elevation = 1f;
}else{
kill();
}
}
}
//AI only updates on the server
@@ -252,6 +289,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
controller.updateUnit();
}
//clear controller when it becomes invalid
if(!controller.isValidController()){
resetController();
}
//do not control anything when deactivated
if(deactivated){
controlWeapons(false, false);
@@ -276,9 +318,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
float shake = hitSize / 3f;
Effects.scorch(x, y, (int)(hitSize / 5));
Effect.scorch(x, y, (int)(hitSize / 5));
Fx.explosion.at(this);
Effects.shake(shake, shake, this);
Effect.shake(shake, shake, this);
type.deathSound.at(this);
Events.fire(new UnitDestroyEvent(base()));
@@ -297,7 +339,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(type.wreckRegions[i].found()){
float range = type.hitsize/4f;
Tmp.v1.rnd(range);
Effects.decal(type.wreckRegions[i], x + Tmp.v1.x, y + Tmp.v1.y, rotation - 90);
Effect.decal(type.wreckRegions[i], x + Tmp.v1.x, y + Tmp.v1.y, rotation - 90);
}
}
}

View File

@@ -10,6 +10,7 @@ import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@@ -35,6 +36,27 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
}
}
@Override
@Replace
public void lookAt(float angle){
if(onLiquid()){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta);
}
}
@Override
@Replace
public boolean canShoot(){
return onLiquid();
}
@Override
public void add(){
tleft.clear();
tright.clear();
}
@Override
public void draw(){
float z = Draw.z();
@@ -72,5 +94,10 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
return on.isDeep() ? 1.3f : 1f;
}
public boolean onLiquid(){
Tile tile = tileOn();
return tile != null && tile.floor().isLiquid;
}
}

View File

@@ -24,7 +24,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
/** weapon mount array, never null */
@SyncLocal WeaponMount[] mounts = {};
@ReadOnly transient float range, aimX, aimY;
@ReadOnly transient float aimX, aimY;
@ReadOnly transient boolean isRotate;
boolean isShooting;
float ammo;
@@ -35,16 +35,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
}
}
boolean inRange(Position other){
return within(other, range);
}
void setupWeapons(UnitType def){
mounts = new WeaponMount[def.weapons.size];
range = def.range;
for(int i = 0; i < mounts.length; i++){
mounts[i] = new WeaponMount(def.weapons.get(i));
range = Math.max(range, def.weapons.get(i).bullet.range());
}
}
@@ -82,9 +76,15 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
aimY = y;
}
boolean canShoot(){
return true;
}
/** Update shooting and rotation for this unit. */
@Override
public void update(){
boolean can = canShoot();
for(WeaponMount mount : mounts){
Weapon weapon = mount.weapon;
mount.reload = Math.max(mount.reload - Time.delta * reloadMultiplier, 0);
@@ -97,19 +97,20 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
}
//rotate if applicable
if(weapon.rotate && (mount.rotate || mount.shoot)){
if(weapon.rotate && (mount.rotate || mount.shoot) && can){
float axisX = this.x + Angles.trnsx(rotation - 90, weapon.x, weapon.y),
axisY = this.y + Angles.trnsy(rotation - 90, weapon.x, weapon.y);
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation;
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
}else{
}else if(!weapon.rotate){
mount.rotation = 0;
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
}
//shoot if applicable
if(mount.shoot && //must be shooting
can && //must be able to shoot
(ammo > 0 || !state.rules.unitAmmo || team().rules().infiniteAmmo) && //check ammo
(!weapon.alternate || mount.side == weapon.flipSprite) &&
vel.len() >= mount.weapon.minShootVelocity && //check velocity requirements
@@ -162,7 +163,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
}
boolean parentize = ammo.keepVelocity;
Effects.shake(weapon.shake, weapon.shake, x, y);
Effect.shake(weapon.shake, weapon.shake, x, y);
weapon.ejectEffect.at(x, y, rotation * side);
ammo.shootEffect.at(x, y, rotation, parentize ? this : null);
ammo.smokeEffect.at(x, y, rotation, parentize ? this : null);

View File

@@ -5,48 +5,151 @@ import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.indexer;
import static mindustry.Vars.*;
public class AIController implements UnitController{
protected static final Vec2 vec = new Vec2();
protected static final int timerTarget = 0;
protected Unit unit;
protected Teamc target;
protected Interval timer = new Interval(4);
/** main target that is being faced */
protected Teamc target;
/** targets for each weapon */
protected Teamc[] targets = {};
{
timer.reset(0, Mathf.random(40f));
}
protected void targetClosestAllyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getAllied(unit.team, flag));
if(target != null) this.target = target.build;
@Override
public void updateUnit(){
updateTargeting();
updateMovement();
}
protected void targetClosestEnemyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getEnemy(unit.team, flag));
if(target != null) this.target = target.build;
protected void updateMovement(){
}
protected void updateTargeting(){
if(unit.hasWeapons()){
updateWeapons();
}
}
protected void updateWeapons(){
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
float rotation = unit.rotation - 90;
boolean ret = retarget();
if(ret){
target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
}
if(Units.invalidateTarget(target, unit.team, unit.x, unit.y)){
target = null;
}
for(int i = 0; i < targets.length; i++){
WeaponMount mount = unit.mounts[i];
Weapon weapon = mount.weapon;
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){
targets[i] = target;
}else{
if(ret){
targets[i] = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
}
if(Units.invalidateTarget(targets[i], unit.team, mountX, mountY, weapon.bullet.range())){
targets[i] = null;
}
}
boolean shoot = false;
if(targets[i] != null){
shoot = targets[i].within(mountX, mountY, weapon.bullet.range());
if(shoot){
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
mount.aimX = to.x;
mount.aimY = to.y;
}
}
mount.shoot = shoot;
mount.rotate = shoot;
}
}
protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
return target == null ? null : target.build;
}
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground);
}
protected boolean retarget(){
return timer.get(timerTarget, 30);
}
protected void targetClosest(){
Teamc newTarget = Units.closestTarget(unit.team, unit.x, unit.y, Math.max(unit.range(), unit.type().range), u -> (unit.type().targetAir && u.isFlying()) || (unit.type().targetGround && !u.isFlying()));
if(newTarget != null){
target = newTarget;
}
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
return target(x, y, range, air, ground);
}
protected void init(){
}
protected void circle(Position target, float circleLength){
circle(target, circleLength, unit.type().speed);
}
protected void circle(Position target, float circleLength, float speed){
if(target == null) return;
vec.set(target).sub(unit);
if(vec.len() < circleLength){
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed * Time.delta);
unit.moveAt(vec);
}
protected void moveTo(Position target, float circleLength){
if(target == null) return;
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
@Override
public void unit(Unit unit){
if(this.unit == unit) return;

View File

@@ -53,7 +53,8 @@ public class BuildPlan{
}
public static Object pointConfig(Object config, Cons<Point2> cons){
/** Transforms the internal position of this config using the specified function, and return the result. */
public static Object pointConfig(Block block, Object config, Cons<Point2> cons){
if(config instanceof Point2){
config = ((Point2)config).cpy();
cons.get((Point2)config);
@@ -65,14 +66,15 @@ public class BuildPlan{
cons.get(result[i++]);
}
config = result;
}else if(block != null){
config = block.pointConfig(config, cons);
}
return config;
}
/** If this requests's config is a Point2 or an array of Point2s, this returns a copy of them for transformation.
* Otherwise does nothing. */
/** Transforms the internal position of this config using the specified function. */
public void pointConfig(Cons<Point2> cons){
this.config = pointConfig(this.config, cons);
this.config = pointConfig(block, this.config, cons);
}
public BuildPlan copy(){

View File

@@ -1,35 +0,0 @@
package mindustry.entities.units;
public class StateMachine{
private UnitState state;
public void update(){
if(state != null) state.update();
}
public void set(UnitState next){
if(next == state) return;
if(state != null) state.exited();
this.state = next;
if(next != null) next.entered();
}
public UnitState current(){
return state;
}
public boolean is(UnitState state){
return this.state == state;
}
public interface UnitState{
default void entered(){
}
default void exited(){
}
default void update(){
}
}
}

View File

@@ -3,7 +3,7 @@ package mindustry.entities.units;
import arc.*;
public enum UnitCommand{
attack, retreat, rally;
attack, retreat, rally, idle;
private final String localized;
public static final UnitCommand[] all = values();

View File

@@ -5,6 +5,7 @@ import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.net.*;
import mindustry.type.*;
import mindustry.world.*;
@@ -87,6 +88,14 @@ public class EventType{
}
}
public static class ClientPreConnectEvent{
public final Host host;
public ClientPreConnectEvent(Host host){
this.host = host;
}
}
public static class PlayerChatEvent{
public final Player player;
public final String message;
@@ -157,24 +166,13 @@ public class EventType{
}
}
/** Called when the player taps a block. */
public static class TapEvent{
public final Building tile;
public final Player player;
public TapEvent(Building tile, Player player){
this.tile = tile;
this.player = player;
}
}
/** Called when the player sets a specific block. */
public static class TapConfigEvent{
/** Called when the configures sets a specific block. */
public static class ConfigEvent{
public final Building tile;
public final Player player;
public final Object value;
public TapConfigEvent(Building tile, Player player, Object value){
public ConfigEvent(Building tile, Player player, Object value){
this.tile = tile;
this.player = player;
this.value = value;

View File

@@ -29,46 +29,26 @@ public class Objectives{
}
}
public static class SectorWave extends SectorObjective{
public int wave;
public static class SectorComplete extends SectorObjective{
public SectorWave(SectorPreset zone, int wave){
public SectorComplete(SectorPreset zone){
this.preset = zone;
this.wave = wave;
}
protected SectorWave(){}
protected SectorComplete(){}
@Override
public boolean complete(){
return preset.bestWave() >= wave;
return preset.sector.isCaptured();
}
@Override
public String display(){
return Core.bundle.format("requirement.wave", wave, preset.localizedName);
}
}
public static class Launched extends SectorObjective{
public Launched(SectorPreset zone){
this.preset = zone;
}
protected Launched(){}
@Override
public boolean complete(){
return preset.hasLaunched();
}
@Override
public String display(){
return Core.bundle.format("requirement.core", preset.localizedName);
return Core.bundle.format("requirement.capture", preset.localizedName);
}
}
//TODO merge
public abstract static class SectorObjective implements Objective{
public @NonNull SectorPreset preset;
}

View File

@@ -94,6 +94,8 @@ public class Rules{
public Team defaultTeam = Team.sharded;
/** team of the enemy in waves/sectors */
public Team waveTeam = Team.crux;
/** name of the custom mode that this ruleset describes, or null. */
public @Nullable String modeName;
/** special tags for additional info */
public StringMap tags = new StringMap();

View File

@@ -165,9 +165,10 @@ public class Saves{
}
public void deleteAll(){
saves.clear();
for(Fi file : saveDirectory.list()){
file.delete();
for(SaveSlot slot : saves.copy()){
if(!slot.isSector()){
slot.delete();
}
}
}
@@ -187,7 +188,7 @@ public class Saves{
current = this;
totalPlaytime = meta.timePlayed;
savePreview();
}catch(Exception e){
}catch(Throwable e){
throw new SaveException(e);
}
}
@@ -270,7 +271,7 @@ public class Saves{
mods.removeAll(Vars.mods.getModStrings());
if(!mods.isEmpty()){
ui.showConfirm("$warning", Core.bundle.format("mod.missing", mods.toString("\n")), run);
ui.showConfirm("@warning", Core.bundle.format("mod.missing", mods.toString("\n")), run);
}else{
run.run();
}

View File

@@ -42,7 +42,6 @@ import static mindustry.Vars.*;
public class Schematics implements Loadable{
private static final Schematic tmpSchem = new Schematic(new Seq<>(), new StringMap(), 0, 0);
private static final Schematic tmpSchem2 = new Schematic(new Seq<>(), new StringMap(), 0, 0);
public static final String base64Header = "bXNjaAB";
private static final byte[] header = {'m', 's', 'c', 'h'};
private static final byte version = 1;
@@ -584,7 +583,7 @@ public class Schematics implements Loadable{
int ox = schem.width/2, oy = schem.height/2;
schem.tiles.each(req -> {
req.config = BuildPlan.pointConfig(req.config, p -> {
req.config = BuildPlan.pointConfig(req.block, req.config, p -> {
int cx = p.x, cy = p.y;
int lx = cx;

View File

@@ -64,7 +64,7 @@ public class SectorInfo{
//update core items
coreItems.clear();
CoreEntity entity = state.rules.defaultTeam.core();
CoreBuild entity = state.rules.defaultTeam.core();
if(entity != null){
ItemModule items = entity.items;
@@ -97,7 +97,7 @@ public class SectorInfo{
updateCoreDeltas();
}
CoreEntity ent = state.rules.defaultTeam.core();
CoreBuild ent = state.rules.defaultTeam.core();
//refresh throughput
if(time.get(refreshPeriod)){
@@ -141,7 +141,7 @@ public class SectorInfo{
}
private void updateCoreDeltas(){
CoreEntity ent = state.rules.defaultTeam.core();
CoreBuild ent = state.rules.defaultTeam.core();
for(int i = 0; i < lastCoreItems.length; i++){
lastCoreItems[i] = ent == null ? 0 : ent.items.get(i);
}
@@ -160,5 +160,9 @@ public class SectorInfo{
/** mean in terms of items produced per refresh rate (currently, per second) */
public float mean;
public String toString(){
return mean + "";
}
}
}

View File

@@ -36,12 +36,12 @@ public class Team implements Comparable<Team>{
blue = new Team(5, "blue", Color.royal.cpy());
static{
Mathf.random.setSeed(8);
Mathf.rand.setSeed(8);
//create the whole 256 placeholder teams
for(int i = 6; i < all.length; i++){
new Team(i, "team#" + i, Color.HSVtoRGB(360f * Mathf.random(), 100f * Mathf.random(0.6f, 1f), 100f * Mathf.random(0.8f, 1f), 1f));
}
Mathf.random.setSeed(new Rand().nextLong());
Mathf.rand.setSeed(new Rand().nextLong());
}
public static Team get(int id){
@@ -91,7 +91,7 @@ public class Team implements Comparable<Team>{
return state.teams.get(this);
}
public @Nullable CoreEntity core(){
public @Nullable CoreBuild core(){
return data().core();
}
@@ -103,7 +103,7 @@ public class Team implements Comparable<Team>{
return state.teams.areEnemies(this, other);
}
public Seq<CoreEntity> cores(){
public Seq<CoreBuild> cores(){
return state.teams.cores(this);
}

View File

@@ -5,7 +5,10 @@ import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.ai.*;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
@@ -21,15 +24,15 @@ public class Teams{
active.add(get(Team.crux));
}
public @Nullable CoreEntity closestEnemyCore(float x, float y, Team team){
public @Nullable CoreBuild closestEnemyCore(float x, float y, Team team){
for(Team enemy : team.enemies()){
CoreEntity tile = Geometry.findClosest(x, y, enemy.cores());
CoreBuild tile = Geometry.findClosest(x, y, enemy.cores());
if(tile != null) return tile;
}
return null;
}
public @Nullable CoreEntity closestCore(float x, float y, Team team){
public @Nullable CoreBuild closestCore(float x, float y, Team team){
return Geometry.findClosest(x, y, get(team).cores);
}
@@ -37,10 +40,10 @@ public class Teams{
return get(team).enemies;
}
public boolean eachEnemyCore(Team team, Boolf<CoreEntity> ret){
public boolean eachEnemyCore(Team team, Boolf<CoreBuild> ret){
for(TeamData data : active){
if(areEnemies(team, data.team)){
for(CoreEntity tile : data.cores){
for(CoreBuild tile : data.cores){
if(ret.get(tile)){
return true;
}
@@ -68,12 +71,12 @@ public class Teams{
return map[team.id];
}
public Seq<CoreEntity> playerCores(){
public Seq<CoreBuild> playerCores(){
return get(state.rules.defaultTeam).cores;
}
/** Do not modify! */
public Seq<CoreEntity> cores(Team team){
public Seq<CoreBuild> cores(Team team){
return get(team).cores;
}
@@ -98,7 +101,7 @@ public class Teams{
return active;
}
public void registerCore(CoreEntity core){
public void registerCore(CoreBuild core){
TeamData data = get(core.team());
//add core if not present
if(!data.cores.contains(core)){
@@ -113,7 +116,7 @@ public class Teams{
}
}
public void unregisterCore(CoreEntity entity){
public void unregisterCore(CoreBuild entity){
TeamData data = get(entity.team());
//remove core
data.cores.remove(entity);
@@ -143,11 +146,17 @@ public class Teams{
}
public class TeamData{
public final Seq<CoreEntity> cores = new Seq<>();
public final Seq<CoreBuild> cores = new Seq<>();
public final Team team;
public final BaseAI ai;
public Team[] enemies = {};
/** Planned blocks for drones. This is usually only blocks that have been broken. */
public Queue<BlockPlan> blocks = new Queue<>();
/** The current command for units to follow. */
public UnitCommand command = UnitCommand.attack;
/** Target items to mine. */
public Seq<Item> mineItems = Seq.with(Items.copper, Items.lead, Items.titanium, Items.thorium);
public TeamData(Team team){
this.team = team;
@@ -166,7 +175,7 @@ public class Teams{
return cores.isEmpty();
}
public @Nullable CoreEntity core(){
public @Nullable CoreBuild core(){
return cores.isEmpty() ? null : cores.first();
}

View File

@@ -157,7 +157,7 @@ public class Drawf{
Lines.stroke(12f * scale);
Lines.precise(true);
Lines.line(line, x + Tmp.v1.x, y + Tmp.v1.y, x2 - Tmp.v1.x, y2 - Tmp.v1.y, CapStyle.none, 0f);
Lines.line(line, x + Tmp.v1.x, y + Tmp.v1.y, x2 - Tmp.v1.x, y2 - Tmp.v1.y, false, 0f);
Lines.precise(false);
Lines.stroke(1f);

View File

@@ -172,7 +172,7 @@ public class LoadRenderer implements Disposable{
Lines.poly(w/2, h/2, 4, rad2);
if(assets.isLoaded("tech")){
BitmapFont font = assets.get("tech");
Font font = assets.get("tech");
font.getData().markupEnabled = true;
int panei = 0;
@@ -461,10 +461,10 @@ public class LoadRenderer implements Disposable{
String key = name.contains("script") ? "scripts" : name.contains("content") ? "content" : name.contains("mod") ? "mods" : name.contains("msav") ||
name.contains("maps") ? "map" : name.contains("ogg") || name.contains("mp3") ? "sound" : name.contains("png") ? "image" : "system";
BitmapFont font = assets.get("tech");
Font font = assets.get("tech");
font.setColor(Pal.accent);
Draw.color(Color.black);
font.draw(red + "[[[[ " +key + " ]]\n\n"+orange+"<" + Version.modifier + " " + (Version.build == 0 ? " [init]" : Version.build == -1 ? " custom" : " " + Version.build) + ">", w/2f, h/2f + 110*s, Align.center);
font.draw(red + "[[[[ " + key + " ]]\n"+orange+"<" + Version.modifier + " " + (Version.build == 0 ? " [init]" : Version.build == -1 ? " custom" : " " + Version.build) + ">", w/2f, h/2f + 110*s, Align.center);
}
Lines.precise(false);

View File

@@ -172,7 +172,7 @@ public class MinimapRenderer implements Disposable{
}
public void drawLabel(float x, float y, String text, Color color){
BitmapFont font = Fonts.outline;
Font font = Fonts.outline;
GlyphLayout l = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
boolean ints = font.usesIntegerPositions();
font.getData().setScale(1 / 1.5f / Scl.scl(1f));

View File

@@ -42,10 +42,10 @@ public class OverlayRenderer{
if(!rect.setSize(Core.camera.width * 0.9f, Core.camera.height * 0.9f)
.setCenter(Core.camera.position.x, Core.camera.position.y).contains(player.x, player.y)){
Tmp.v1.set(player.x, player.y).sub(player).setLength(indicatorLength);
Tmp.v1.set(player).sub(Vars.player).setLength(indicatorLength);
Lines.stroke(2f, player.team().color);
Lines.lineAngle(player.x + Tmp.v1.x, player.y + Tmp.v1.y, Tmp.v1.angle(), 4f);
Lines.stroke(2f, Vars.player.team().color);
Lines.lineAngle(Vars.player.x + Tmp.v1.x, Vars.player.y + Tmp.v1.y, Tmp.v1.angle(), 4f);
Draw.reset();
}
}

View File

@@ -10,6 +10,8 @@ public class Pal{
sap = Color.valueOf("665c9f"),
spore = Color.valueOf("7457ce"),
shield = Color.valueOf("ffd37f").a(0.7f),
shieldIn = Color.black.cpy().a(0f),

View File

@@ -24,11 +24,12 @@ public class Shaders{
public static PlanetShader planet;
public static PlanetGridShader planetGrid;
public static AtmosphereShader atmosphere;
public static MeshShader mesh = new MeshShader();
public static MeshShader mesh;
public static Shader unlit;
public static Shader screenspace;
public static void init(){
mesh = new MeshShader();
blockbuild = new BlockBuild();
try{
shield = new ShieldShader();

View File

@@ -18,6 +18,10 @@ public class Trail{
points = new Seq<>(length);
}
public void clear(){
points.clear();
}
public void draw(Color color, float width){
Draw.color(color);

View File

@@ -33,7 +33,7 @@ public class PlanetRenderer implements Disposable{
/** The sun/main planet of the solar system from which everything is rendered. */
public final Planet solarSystem = Planets.sun;
/** Planet being looked at. */
public Planet planet = Planets.starter;
public Planet planet = Planets.serpulo;
/** Camera used for rendering. */
public Camera3D cam = new Camera3D();
/** Raw vertex batch. */

View File

@@ -13,7 +13,6 @@ import arc.scene.ui.layout.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -74,7 +73,7 @@ public class DesktopInput extends InputHandler{
Core.keybinds.get(Binding.schematic_flip_y).key.toString())).style(Styles.outlineLabel);
b.row();
b.table(a -> {
a.button("$schematic.add", Icon.save, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null);
a.button("@schematic.add", Icon.save, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null);
});
}).margin(6f);
});
@@ -177,7 +176,7 @@ public class DesktopInput extends InputHandler{
public void update(){
super.update();
if(net.active() && Core.input.keyTap(Binding.player_list)){
if(net.active() && Core.input.keyTap(Binding.player_list) && (scene.getKeyboardFocus() == null || scene.getKeyboardFocus().isDescendantOf(ui.listfrag.content))){
ui.listfrag.toggle();
}
@@ -197,7 +196,7 @@ public class DesktopInput extends InputHandler{
Core.camera.position.lerpDelta(player, Core.settings.getBool("smoothcamera") ? 0.08f : 1f);
}
shouldShoot = true;
shouldShoot = !scene.hasMouse();
if(!scene.hasMouse()){
if(Core.input.keyDown(Binding.control) && Core.input.keyTap(Binding.select)){
@@ -330,20 +329,20 @@ public class DesktopInput extends InputHandler{
table.button(Icon.paste, Styles.clearPartiali, () -> {
ui.schematics.show();
});
}).tooltip("Schematics");
table.button(Icon.tree, Styles.clearPartiali, () -> {
ui.research.show();
}).visible(() -> state.isCampaign());
}).visible(() -> state.isCampaign()).tooltip("Research");
table.button(Icon.map, Styles.clearPartiali, () -> {
ui.planet.show();
}).visible(() -> state.isCampaign());
}).visible(() -> state.isCampaign()).tooltip("Planet Map");
table.button(Icon.up, Styles.clearPartiali, () -> {
ui.planet.show(state.getSector(), player.team().core());
}).visible(() -> state.isCampaign())
.disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements));
.disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements)).tooltip("Launch Core");
}
void pollInput(){
@@ -567,10 +566,18 @@ public class DesktopInput extends InputHandler{
protected void updateMovement(Unit unit){
boolean omni = !(unit instanceof WaterMovec);
boolean legs = unit.isGrounded();
boolean ground = unit.isGrounded();
float strafePenalty = legs ? 1f : Mathf.lerp(1f, unit.type().strafePenalty, Angles.angleDist(unit.vel().angle(), unit.rotation()) / 180f);
float speed = unit.type().speed * Mathf.lerp(1f, unit.type().canBoost ? unit.type().boostMultiplier : 1f, unit.elevation()) * strafePenalty;
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 instanceof Commanderc && ((Commanderc)unit).isCommanding()){
//add a tiny multiplier to let units catch up just in case
baseSpeed = ((Commanderc)unit).minFormationSpeed() * 0.98f;
}
float speed = baseSpeed * Mathf.lerp(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());
@@ -595,8 +602,8 @@ public class DesktopInput extends InputHandler{
unit.moveAt(movement);
}else{
unit.moveAt(Tmp.v2.trns(unit.rotation, movement.len()));
if(!movement.isZero() && legs){
unit.vel.rotateTo(movement.angle(), unit.type().rotateSpeed * Time.delta);
if(!movement.isZero() && ground){
unit.vel.rotateTo(movement.angle(), unit.type().rotateSpeed);
}
}
@@ -611,22 +618,12 @@ public class DesktopInput extends InputHandler{
if(unit instanceof Payloadc){
Payloadc pay = (Payloadc)unit;
if(Core.input.keyTap(Binding.pickupCargo) && pay.payloads().size < unit.type().payloadCapacity){
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitsize * 2.5f, u -> u.isAI() && u.isGrounded() && u.mass() < unit.mass() && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
if(target != null){
Call.pickupUnitPayload(player, target);
}else if(!pay.hasPayload()){
Building tile = world.buildWorld(pay.x(), pay.y());
if(tile != null && tile.team() == unit.team){
Call.pickupBlockPayload(player, tile);
}
}
if(Core.input.keyTap(Binding.pickupCargo)){
tryPickupPayload();
}
if(Core.input.keyTap(Binding.dropCargo)){
Call.dropPayload(player, player.x, player.y);
pay.dropLastPayload();
tryDropPayload();
}
}

View File

@@ -86,12 +86,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
@Remote(called = Loc.server, unreliable = true)
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
if(tile == null || tile.build == null || tile.build.items == null) return;
public static void transferItemTo(Item item, int amount, float x, float y, Building build){
if(build == null || build.items == null) return;
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
Time.run(i * 3, () -> createItemTransfer(item, amount, x, y, tile, () -> {}));
Time.run(i * 3, () -> createItemTransfer(item, amount, x, y, build, () -> {}));
}
tile.build.items.add(item, amount);
build.items.add(item, amount);
}
public static void createItemTransfer(Item item, int amount, float x, float y, Position to, Runnable done){
@@ -126,7 +126,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(tile != null && tile.team() == unit.team && pay.payloads().size < unit.type().payloadCapacity
&& unit.within(tile, tilesize * tile.block.size * 1.2f)){
//pick up block directly
if(tile.block().buildVisibility != BuildVisibility.hidden && tile.block().size <= 2){
if(tile.block().buildVisibility != BuildVisibility.hidden && tile.block().size <= 2 && tile.canPickup()){
pay.pickup(tile);
}else{ //pick up block payload
Payload current = tile.getPayload();
@@ -206,35 +206,30 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
);
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void tileTapped(Player player, Building tile){
if(tile == null || player == null) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.tapTile, tile.tile(), action -> {}))) throw new ValidateException(player, "Player cannot tap a tile.");
tile.tapped(player);
Core.app.post(() -> Events.fire(new TapEvent(tile, player)));
}
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void tileConfig(Player player, Building tile, @Nullable Object value){
if(tile == null) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.configure, tile.tile(), action -> action.config = value))) throw new ValidateException(player, "Player cannot configure a tile.");
tile.configured(player, value);
Core.app.post(() -> Events.fire(new TapConfigEvent(tile, player, value)));
Core.app.post(() -> Events.fire(new ConfigEvent(tile, player, value)));
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void unitControl(Player player, @Nullable Unit unit){
//clear player unit when they possess a core
if((unit instanceof BlockUnitc && ((BlockUnitc)unit).tile() instanceof CoreEntity)){
if((unit instanceof BlockUnitc && ((BlockUnitc)unit).tile() instanceof CoreBuild)){
Fx.spawn.at(player);
if(net.client()){
control.input.controlledType = null;
}
player.clearUnit();
player.deathTimer(60f); //for instant respawn
player.deathTimer = 61f;
}else if(unit == null){ //just clear the unit (is this used?)
player.clearUnit();
//make sure it's AI controlled, so players can't overwrite each other
}else if(unit.isAI() && unit.team == player.team() && !unit.deactivated){
}else if(unit.isAI() && unit.team == player.team() && !unit.deactivated()){
player.unit(unit);
Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit));
if(!player.dead()){
@@ -243,19 +238,19 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void unitClear(Player player){
//no free core teleports?
if(!player.dead() && player.unit().spawnedByCore) return;
if(player == null || !player.dead() && player.unit().spawnedByCore) return;
Fx.spawn.at(player);
player.clearUnit();
player.deathTimer(60f); //for instant respawn
player.deathTimer = 61f; //for instant respawn
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void unitCommand(Player player){
if(player.dead() || !(player.unit() instanceof Commanderc)) return;
if(player == null || player.dead() || !(player.unit() instanceof Commanderc)) return;
Commanderc commander = (Commanderc)player.unit();
@@ -311,7 +306,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
if(controlledType != null && player.dead()){
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.deactivated);
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.deactivated());
if(unit != null){
Call.unitControl(player, unit);
@@ -321,7 +316,7 @@ 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.deactivated);
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.deactivated());
if(unit == null && controlledType == UnitTypes.block){
unit = world.buildWorld(player.x, player.y) instanceof ControlBlock ? ((ControlBlock)world.buildWorld(player.x, player.y)).unit() : null;
}
@@ -336,6 +331,34 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
}
public void tryPickupPayload(){
Unit unit = player.unit();
if(!(unit instanceof Payloadc)) return;
Payloadc pay = (Payloadc)unit;
if(pay.payloads().size >= unit.type().payloadCapacity) return;
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitsize * 2.5f, u -> u.isAI() && u.isGrounded() && u.mass() < unit.mass() && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
if(target != null){
Call.pickupUnitPayload(player, target);
}else if(!pay.hasPayload()){
Building tile = world.buildWorld(pay.x(), pay.y());
if(tile != null && tile.team == unit.team){
Call.pickupBlockPayload(player, tile);
}
}
}
public void tryDropPayload(){
Unit unit = player.unit();
if(!(unit instanceof Payloadc)) return;
Payloadc pay = (Payloadc)unit;
Call.dropPayload(player, player.x, player.y);
pay.dropLastPayload();
}
public float getMouseX(){
return Core.input.mouseX();
}
@@ -396,18 +419,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
protected void showSchematicSave(){
if(lastSchematic == null) return;
ui.showTextInput("$schematic.add", "$name", "", text -> {
ui.showTextInput("@schematic.add", "@name", "", text -> {
Schematic replacement = schematics.all().find(s -> s.name().equals(text));
if(replacement != null){
ui.showConfirm("$confirm", "$schematic.replace", () -> {
ui.showConfirm("@confirm", "@schematic.replace", () -> {
schematics.overwrite(replacement, lastSchematic);
ui.showInfoFade("$schematic.saved");
ui.showInfoFade("@schematic.saved");
ui.schematics.showInfo(replacement);
});
}else{
lastSchematic.tags.put("name", text);
schematics.add(lastSchematic);
ui.showInfoFade("$schematic.saved");
ui.showInfoFade("@schematic.saved");
ui.schematics.showInfo(lastSchematic);
}
});
@@ -746,7 +769,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//call tapped event
if(!consumed && tile.interactable(player.team())){
Call.tileTapped(player, tile);
tile.tapped();
}
//consume tap event if necessary
@@ -844,7 +867,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public @Nullable Unit selectedUnit(){
Unit unit = Units.closest(player.team(), Core.input.mouseWorld().x, Core.input.mouseWorld().y, 40f, u -> u.isAI() && !u.deactivated);
Unit unit = Units.closest(player.team(), Core.input.mouseWorld().x, Core.input.mouseWorld().y, 40f, u -> u.isAI() && !u.deactivated());
if(unit != null){
unit.hitbox(Tmp.r1);
Tmp.r1.grow(6f);
@@ -901,7 +924,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public boolean canShoot(){
return block == null && !Core.scene.hasMouse() && !onConfigurable() && !isDroppingItem() && !(player.builder().updateBuilding() && player.builder().isBuilding());
return block == null && !onConfigurable() && !isDroppingItem() && !(player.builder().updateBuilding() && player.builder().isBuilding()) &&
!(player.unit() instanceof Mechc && player.unit().isFlying());
}
public boolean onConfigurable(){
@@ -1074,192 +1098,4 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public int x, y, rotation;
public boolean last;
}
//TODO implement all of this!
/*
protected void updateKeyboard(){
Tile tile = world.tileWorld(x, y);
boolean canMove = !Core.scene.hasKeyboard() || ui.minimapfrag.shown();
isBoosting = Core.input.keyDown(Binding.dash) && !mech.flying;
//if player is in solid block
if(tile != null && tile.solid()){
isBoosting = true;
}
float speed = isBoosting && !mech.flying ? mech.boostSpeed : mech.speed;
if(mech.flying){
//prevent strafing backwards, have a penalty for doing so
float penalty = 0.2f; //when going 180 degrees backwards, reduce speed to 0.2x
speed *= Mathf.lerp(1f, penalty, Angles.angleDist(rotation, velocity.angle()) / 180f);
}
movement.setZero();
float xa = Core.input.axis(Binding.move_x);
float ya = Core.input.axis(Binding.move_y);
if(!(Core.scene.getKeyboardFocus() instanceof TextField)){
movement.y += ya * speed;
movement.x += xa * speed;
}
if(Core.input.keyDown(Binding.mouse_move)){
movement.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * 0.005f, -1, 1) * speed;
movement.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * 0.005f, -1, 1) * speed;
}
Vec2 vec = Core.input.mouseWorld(control.input.getMouseX(), control.input.getMouseY());
pointerX = vec.x;
pointerY = vec.y;
updateShooting();
movement.limit(speed).scl(Time.delta());
if(canMove){
velocity.add(movement.x, movement.y);
}else{
isShooting = false;
}
float prex = x, prey = y;
updateVelocityStatus();
moved = dst(prex, prey) > 0.001f;
if(canMove){
float baseLerp = mech.getRotationAlpha(this);
if(!isShooting() || !mech.faceTarget){
if(!movement.isZero()){
rotation = Mathf.slerpDelta(rotation, mech.flying ? velocity.angle() : movement.angle(), 0.13f * baseLerp);
}
}else{
float angle = control.input.mouseAngle(x, y);
this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f * baseLerp);
}
}
}
protected void updateShooting(){
if(!state.isEditor() && isShooting() && mech.canShoot(this)){
weapons.update(this);
//if(!mech.turnCursor){
//shoot forward ignoring cursor
//mech.weapon.update(this, x + Angles.trnsx(rotation, mech.weapon.targetDistance), y + Angles.trnsy(rotation, mech.weapon.targetDistance));
//}else{
//mech.weapon.update(this, pointerX, pointerY);
//}
}
}
protected void updateTouch(){
if(Units.invalidateTarget(target, this) &&
!(target instanceof Building && ((Building)target).damaged() && target.isValid() && target.team() == team && mech.canHeal && dst(target) < mech.range && !(((Building)target).block instanceof BuildBlock))){
target = null;
}
if(state.isEditor()){
target = null;
}
float targetX = Core.camera.position.x, targetY = Core.camera.position.y;
float attractDst = 15f;
float speed = isBoosting && !mech.flying ? mech.boostSpeed : mech.speed;
if(moveTarget != null && !moveTarget.dead()){
targetX = moveTarget.getX();
targetY = moveTarget.getY();
boolean tapping = moveTarget instanceof Building && moveTarget.team() == team;
attractDst = 0f;
if(tapping){
velocity.setAngle(angleTo(moveTarget));
}
if(dst(moveTarget) <= 2f * Time.delta()){
if(tapping && !dead()){
Tile tile = ((Building)moveTarget).tile;
tile.tapped(this);
}
moveTarget = null;
}
}else{
moveTarget = null;
}
movement.set((targetX - x) / Time.delta(), (targetY - y) / Time.delta()).limit(speed);
movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f));
if(dst(targetX, targetY) < attractDst){
movement.setZero();
}
float expansion = 3f;
hitbox(rect);
rect.x -= expansion;
rect.y -= expansion;
rect.width += expansion * 2f;
rect.height += expansion * 2f;
isBoosting = collisions.overlapsTile(rect) || dst(targetX, targetY) > 85f;
velocity.add(movement.scl(Time.delta()));
if(velocity.len() <= 0.2f && mech.flying){
rotation += Mathf.sin(Time.time() + id * 99, 10f, 1f);
}else if(target == null){
rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len() / 10f);
}
float lx = x, ly = y;
updateVelocityStatus();
moved = dst(lx, ly) > 0.001f;
if(mech.flying){
//hovering effect
x += Mathf.sin(Time.time() + id * 999, 25f, 0.08f);
y += Mathf.cos(Time.time() + id * 999, 25f, 0.08f);
}
//update shooting if not building, not mining and there's ammo left
if(!isBuilding() && mineTile() == null){
//autofire
if(target == null){
isShooting = false;
if(Core.settings.getBool("autotarget")){
target = Units.closestTarget(team, x, y, mech.range, u -> u.team() != Team.derelict, u -> u.team() != Team.derelict);
if(mech.canHeal && target == null){
target = Geometry.findClosest(x, y, indexer.getDamaged(Team.sharded));
if(target != null && dst(target) > mech.range){
target = null;
}else if(target != null){
target = ((Tile)target).entity;
}
}
if(target != null){
mineTile(null);
}
}
}else if(target.isValid() || (target instanceof Building && ((Building)target).damaged() && target.team() == team && mech.canHeal && dst(target) < mech.range)){
//rotate toward and shoot the target
if(mech.faceTarget){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f);
}
Vec2 intercept = Predict.intercept(this, target, getWeapon().bullet.speed);
pointerX = intercept.x;
pointerY = intercept.y;
updateShooting();
isShooting = true;
}
}
}
*/
}

View File

@@ -243,7 +243,7 @@ public class MobileInput extends InputHandler implements GestureListener{
group.fill(t -> {
t.visible(() -> (player.builder().isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get());
t.bottom().left();
t.button("$cancel", Icon.cancel, () -> {
t.button("@cancel", Icon.cancel, () -> {
player.builder().clearBuilding();
selectRequests.clear();
mode = none;
@@ -422,7 +422,6 @@ public class MobileInput extends InputHandler implements GestureListener{
lastSchematic = schem;
}
@Override
public boolean touchDown(int screenX, int screenY, int pointer, KeyCode button){
if(state.isMenu()) return false;
@@ -497,9 +496,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}else{
Tile tile = tileAt(screenX, screenY);
if(tile == null || tile.build == null) return false;
tryDropItems(tile.build, Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
tryDropItems(tile == null ? null : tile.build, Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
}
return false;
}
@@ -535,10 +532,10 @@ public class MobileInput extends InputHandler implements GestureListener{
lineMode = true;
if(mode == breaking){
Fx.tapBlock.at(cursor.worldx(), cursor.worldy(), 1f);
if(!state.isPaused()) Fx.tapBlock.at(cursor.worldx(), cursor.worldy(), 1f);
}else if(block != null){
updateLine(lineStartX, lineStartY, cursor.x, cursor.y);
Fx.tapBlock.at(cursor.worldx() + block.offset, cursor.worldy() + block.offset, block.size);
if(!state.isPaused()) Fx.tapBlock.at(cursor.worldx() + block.offset, cursor.worldy() + block.offset, block.size);
}
}
@@ -575,9 +572,18 @@ public class MobileInput extends InputHandler implements GestureListener{
}
//apply command on double tap
if(count == 2 && Mathf.within(worldx, worldy, player.unit().x, player.unit().y, player.unit().hitSize * 2f) &&
player.unit() instanceof Commanderc){
Call.unitCommand(player);
if(count == 2 && Mathf.within(worldx, worldy, player.unit().x, player.unit().y, player.unit().hitSize * 2f)){
if(player.unit() instanceof Commanderc){
Call.unitCommand(player);
}
if(player.unit() instanceof Payloadc){
if(((Payloadc)player.unit()).hasPayload()){
tryDropPayload();
}else{
tryPickupPayload();
}
}
}
}
@@ -755,7 +761,7 @@ public class MobileInput extends InputHandler implements GestureListener{
shiftDeltaX %= tilesize;
shiftDeltaY %= tilesize;
}
}else{
}else if(!renderer.isLanding()){
//pan player
Core.camera.position.x -= deltaX;
Core.camera.position.y -= deltaY;
@@ -788,6 +794,8 @@ public class MobileInput extends InputHandler implements GestureListener{
Rect rect = Tmp.r3;
UnitType type = unit.type();
if(type == null) return;
boolean flying = type.flying;
boolean omni = !(unit instanceof WaterMovec);
boolean legs = unit.isGrounded();
@@ -806,7 +814,16 @@ public class MobileInput extends InputHandler implements GestureListener{
targetPos.set(Core.camera.position);
float attractDst = 15f;
float strafePenalty = legs ? 1f : Mathf.lerp(1f, type.strafePenalty, Angles.angleDist(unit.vel.angle(), unit.rotation) / 180f);
float speed = type.speed * Mathf.lerp(1f, type.canBoost ? type.boostMultiplier : 1f, unit.elevation()) * strafePenalty;
float baseSpeed = unit.type().speed;
//limit speed to minimum formation speed to preserve formation
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
//add a tiny multiplier to let units catch up just in case
baseSpeed = ((Commanderc)unit).minFormationSpeed() * 0.98f;
}
float speed = baseSpeed * Mathf.lerp(1f, type.canBoost ? type.boostMultiplier : 1f, unit.elevation) * strafePenalty;
float range = unit.hasWeapons() ? unit.range() : 0f;
float bulletSpeed = unit.hasWeapons() ? type.weapons.first().bullet.speed : 0f;
float mouseAngle = unit.angleTo(unit.aimX(), unit.aimY());
@@ -898,7 +915,7 @@ public class MobileInput extends InputHandler implements GestureListener{
player.mouseX = intercept.x;
player.mouseY = intercept.y;
player.shooting = true;
player.shooting = !boosted;
unit.aim(player.mouseX, player.mouseY);
}

View File

@@ -37,6 +37,8 @@ public abstract class SaveFileReader{
"command-center", "legacy-command-center"
);
protected int lastRegionLength;
protected void region(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{
counter.resetCount();
int length;
@@ -90,6 +92,7 @@ public abstract class SaveFileReader{
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
public int readChunk(DataInput input, boolean isShort, IORunner<DataInput> runner) throws IOException{
int length = isShort ? input.readUnsignedShort() : input.readInt();
lastRegionLength = length;
runner.accept(input);
return length;
}

View File

@@ -31,6 +31,6 @@ public class SaveMeta{
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
this.secinfo = secinfo;
secinfo.exportRates().each(e -> hasProduction |= e.value > 0.001f);
secinfo.production.each((e, amount) -> hasProduction |= amount.mean > 0.001f);
}
}

View File

@@ -331,6 +331,11 @@ public abstract class SaveVersion extends SaveFileReader{
for(int j = 0; j < amount; j++){
readChunk(stream, true, in -> {
byte typeid = in.readByte();
if(EntityMapping.map(typeid) == null){
in.skipBytes(lastRegionLength - 1);
return;
}
Entityc entity = (Entityc)EntityMapping.map(typeid).get();
entity.read(Reads.get(in));
entity.add();

View File

@@ -3,6 +3,7 @@ package mindustry.io;
import arc.graphics.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.io.*;
import arc.util.pooling.*;
import mindustry.ai.types.*;
@@ -10,10 +11,12 @@ import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.logic.*;
import mindustry.net.Administration.*;
import mindustry.net.Packets.*;
import mindustry.type.*;
@@ -46,7 +49,6 @@ public class TypeIO{
}else if(object instanceof String){
write.b((byte)4);
writeString(write, (String)object);
writeString(write, (String)object);
}else if(object instanceof Content){
Content map = (Content)object;
write.b((byte)5);
@@ -74,11 +76,28 @@ public class TypeIO{
write.b(9);
write.b((byte)map.content.getContentType().ordinal());
write.s(map.content.id);
}else if(object instanceof Boolean){
write.b((byte)10);
write.bool((Boolean)object);
}else if(object instanceof Double){
write.b((byte)11);
write.d((Double)object);
}else if(object instanceof Building){
write.b((byte)12);
write.i(((Building)object).pos());
}else if(object instanceof LAccess){
write.b((byte)13);
write.s(((LAccess)object).ordinal());
}else if(object instanceof byte[]){
write.b((byte)14);
write.i(((byte[])object).length);
write.b((byte[])object);
}else{
throw new IllegalArgumentException("Unknown object type: " + object.getClass());
}
}
@Nullable
public static Object readObject(Reads read){
byte type = read.b();
switch(type){
@@ -92,6 +111,11 @@ public class TypeIO{
case 7: return new Point2(read.i(), read.i());
case 8: byte len = read.b(); Point2[] out = new Point2[len]; for(int i = 0; i < len; i ++) out[i] = Point2.unpack(read.i()); return out;
case 9: return TechTree.getNotNull(content.getByID(ContentType.all[read.b()], read.s()));
case 10: return read.bool();
case 11: return read.d();
case 12: return world.build(read.i());
case 13: return LAccess.all[read.s()];
case 14: int blen = read.i(); byte[] bytes = new byte[blen]; read.b(bytes); return bytes;
default: throw new IllegalArgumentException("Unknown object type: " + type);
}
}
@@ -283,7 +307,7 @@ public class TypeIO{
//make sure player exists
if(player == null) return prev;
return player;
}else if(type == 1){
}else if(type == 1){ //formation controller
int id = read.i();
return prev instanceof FormationAI ? prev : new FormationAI(Groups.unit.getByID(id), null);
}else{
@@ -380,14 +404,22 @@ public class TypeIO{
return AdminAction.values()[read.b()];
}
public static void writeUnitDef(Writes write, UnitType effect){
public static void writeUnitType(Writes write, UnitType effect){
write.s(effect.id);
}
public static UnitType readUnitDef(Reads read){
public static UnitType readUnitType(Reads read){
return content.getByID(ContentType.unit, read.s());
}
public static void writeEffect(Writes write, Effect effect){
write.s(effect.id);
}
public static Effect readEffect(Reads read){
return Effect.get(read.us());
}
public static void writeColor(Writes write, Color color){
write.i(color.rgba());
}

View File

@@ -66,7 +66,7 @@ public abstract class LegacySaveVersion extends SaveVersion{
readChunk(stream, true, in -> {
byte version = in.readByte();
//legacy impl of Building#read()
tile.build.health(stream.readUnsignedShort());
tile.build.health = stream.readUnsignedShort();
byte packedrot = stream.readByte();
byte team = Pack.leftByte(packedrot) == 8 ? stream.readByte() : Pack.leftByte(packedrot);
byte rotation = Pack.rightByte(packedrot);

View File

@@ -0,0 +1,46 @@
package mindustry.logic;
import arc.math.*;
public enum BinaryOp{
add("+", (a, b) -> a + b),
sub("-", (a, b) -> a - b),
mul("*", (a, b) -> a * b),
div("/", (a, b) -> a / b),
mod("%", (a, b) -> a % b),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0),
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1),
lessThan("<", (a, b) -> a < b ? 1 : 0),
lessThanEq("<=", (a, b) -> a <= b ? 1 : 0),
greaterThan(">", (a, b) -> a > b ? 1 : 0),
greaterThanEq(">=", (a, b) -> a >= b ? 1 : 0),
pow("^", Math::pow),
shl(">>", (a, b) -> (int)a >> (int)b),
shr("<<", (a, b) -> (int)a << (int)b),
or("or", (a, b) -> (int)a | (int)b),
and("and", (a, b) -> (int)a & (int)b),
xor("xor", (a, b) -> (int)a ^ (int)b),
max("max", Math::max),
min("min", Math::min),
atan2("atan2", (x, y) -> Mathf.atan2((float)x, (float)y) * Mathf.radDeg),
dst("dst", (x, y) -> Mathf.dst((float)x, (float)y));
public static final BinaryOp[] all = values();
public final OpLambda function;
public final String symbol;
BinaryOp(String symbol, OpLambda function){
this.symbol = symbol;
this.function = function;
}
@Override
public String toString(){
return symbol;
}
interface OpLambda{
double get(double a, double b);
}
}

View File

@@ -0,0 +1,29 @@
package mindustry.logic;
public enum ConditionOp{
equal("==", (a, b) -> Math.abs(a - b) < 0.000001),
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001),
lessThan("<", (a, b) -> a < b),
lessThanEq("<=", (a, b) -> a <= b),
greaterThan(">", (a, b) -> a > b),
greaterThanEq(">=", (a, b) -> a >= b);
public static final ConditionOp[] all = values();
public final CondOpLambda function;
public final String symbol;
ConditionOp(String symbol, CondOpLambda function){
this.symbol = symbol;
this.function = function;
}
@Override
public String toString(){
return symbol;
}
interface CondOpLambda{
boolean get(double a, double b);
}
}

View File

@@ -0,0 +1,6 @@
package mindustry.logic;
/** An object that can be controlled with logic. */
public interface Controllable{
void control(LAccess type, double p1, double p2, double p3, double p4);
}

View File

@@ -0,0 +1,24 @@
package mindustry.logic;
import arc.graphics.*;
import mindustry.graphics.*;
/** The types of data a node field can be. */
public enum DataType{
/** A double. Used for integer calculations as well. */
number(Pal.place),
/** Any type of content, e.g. item. */
content(Color.cyan),
/** A building of a tile. */
building(Pal.items),
/** A unit on the map. */
unit(Pal.health),
/** Java string */
string(Color.royal);
public final Color color;
DataType(Color color){
this.color = color;
}
}

View File

@@ -0,0 +1,41 @@
package mindustry.logic;
import arc.struct.*;
/** Setter/getter enum for logic-controlled objects. */
public enum LAccess{
totalItems,
totalLiquids,
totalPower,
itemCapacity,
liquidCapacity,
powerCapacity,
powerNetStored,
powerNetCapacity,
powerNetIn,
powerNetOut,
health,
heat,
efficiency,
rotation,
x,
y,
shootX,
shootY,
shooting,
team,
//values with parameters are considered controllable
enabled("to"), //"to" is standard for single parameter access
shoot("x", "y", "shoot"),;
public final String[] parameters;
public static final LAccess[] all = values();
public static final LAccess[] senseable = Seq.select(all, t -> t.parameters.length <= 1).toArray(LAccess.class);
public static final LAccess[] controls = Seq.select(all, t -> t.parameters.length > 0).toArray(LAccess.class);
LAccess(String... parameters){
this.parameters = parameters;
}
}

View File

@@ -0,0 +1,195 @@
package mindustry.logic;
import arc.func.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.logic.LExecutor.*;
import mindustry.logic.LStatements.*;
import mindustry.type.*;
import mindustry.world.blocks.logic.*;
/** "Compiles" a sequence of statements into instructions. */
public class LAssembler{
public static ObjectMap<String, Func<String[], LStatement>> customParsers = new ObjectMap<>();
private int lastVar;
/** Maps names to variable IDs. */
ObjectMap<String, BVar> vars = new ObjectMap<>();
/** All instructions to be executed. */
LInstruction[] instructions;
public LAssembler(){
putVar("@counter").value = 0;
putConst("@time", 0);
//add default constants
putConst("false", 0);
putConst("true", 1);
putConst("null", null);
//store base content (TODO hacky?)
for(Item item : Vars.content.items()){
putConst("@" + item.name, item);
}
for(Liquid liquid : Vars.content.liquids()){
putConst("@" + liquid.name, liquid);
}
//store sensor constants
for(LAccess sensor : LAccess.all){
putConst("@" + sensor.name(), sensor);
}
}
public static LAssembler assemble(String data, int maxInstructions){
LAssembler asm = new LAssembler();
Seq<LStatement> st = read(data, maxInstructions);
asm.instructions = st.map(l -> l.build(asm)).filter(l -> l != null).toArray(LInstruction.class);
return asm;
}
public static String write(Seq<LStatement> statements){
StringBuilder out = new StringBuilder();
for(LStatement s : statements){
s.write(out);
out.append("\n");
}
return out.toString();
}
public static Seq<LStatement> read(String data){
return read(data, LogicBlock.maxInstructions);
}
public static Seq<LStatement> read(String data, int max){
//empty data check
if(data == null || data.isEmpty()) return new Seq<>();
Seq<LStatement> statements = new Seq<>();
String[] lines = data.split("[;\n]+");
int index = 0;
for(String line : lines){
//comments
if(line.startsWith("#")) continue;
if(index++ > max) break;
try{
String[] arr;
//yes, I am aware that this can be split with regex, but that's slow and even more incomprehensible
if(line.contains(" ")){
Seq<String> tokens = new Seq<>();
boolean inString = false;
int lastIdx = 0;
for(int i = 0; i < line.length() + 1; i++){
char c = i == line.length() ? ' ' : line.charAt(i);
if(c == '"'){
inString = !inString;
}else if(c == ' ' && !inString){
tokens.add(line.substring(lastIdx, i).replace("\\n", "\n"));
lastIdx = i + 1;
}
}
arr = tokens.toArray(String.class);
}else{
arr = new String[]{line};
}
LStatement st = LogicIO.read(arr);
if(st != null){
statements.add(st);
}else{
//attempt parsing using custom parser if a match is found - this is for mods
String first = arr[0];
if(customParsers.containsKey(first)){
statements.add(customParsers.get(first).get(arr));
}else{
//unparseable statement
statements.add(new InvalidStatement());
}
}
}catch(Exception parseFailed){
//when parsing fails, add a dummy invalid statement
statements.add(new InvalidStatement());
}
}
return statements;
}
/** @return a variable ID by name.
* This may be a constant variable referring to a number or object. */
public int var(String symbol){
symbol = symbol.trim();
//string case
if(symbol.startsWith("\"") && symbol.endsWith("\"")){
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1)).id;
}
try{
double value = Double.parseDouble(symbol);
//this creates a hidden const variable with the specified value
String key = "___" + value;
return putConst(key, value).id;
}catch(NumberFormatException e){
return putVar(symbol).id;
}
}
/** Adds a constant value by name. */
public BVar putConst(String name, Object value){
BVar var = putVar(name);
var.constant = true;
var.value = value;
return var;
}
/** Registers a variable name mapping. */
public BVar putVar(String name){
if(vars.containsKey(name)){
return vars.get(name);
}else{
BVar var = new BVar(lastVar++);
vars.put(name, var);
return var;
}
}
public @Nullable BVar getVar(String name){
return vars.get(name);
}
/** A variable "builder". */
public static class BVar{
public int id;
public boolean constant;
public Object value;
public BVar(int id){
this.id = id;
}
BVar(){}
@Override
public String toString(){
return "BVar{" +
"id=" + id +
", constant=" + constant +
", value=" + value +
'}';
}
}
}

View File

@@ -0,0 +1,441 @@
package mindustry.logic;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.blocks.logic.*;
public class LCanvas extends Table{
private static final Color backgroundCol = Pal.darkMetal.cpy().mul(0.1f), gridCol = Pal.darkMetal.cpy().mul(0.5f);
private static Seq<Runnable> postDraw = new Seq<>();
private Vec2 offset = new Vec2();
DragLayout statements;
StatementElem dragging;
ScrollPane pane;
float targetWidth;
public LCanvas(){
rebuild();
}
/** @return if statement elements should have rows. */
public static boolean useRows(){
return Core.graphics.getWidth() < Scl.scl(900f) * 1.2f;
}
public void rebuild(){
targetWidth = useRows() ? 400f : 900f;
float s = pane != null ? pane.getScrollPercentY() : 0f;
String toLoad = statements != null ? save() : null;
clear();
statements = new DragLayout();
pane = pane(t -> {
t.center();
t.add(statements).pad(2f).center().width(targetWidth);
}).grow().get();
pane.setClip(false);
pane.setFlickScroll(false);
//load old scroll percent
Core.app.post(() -> {
pane.setScrollPercentY(s);
pane.updateVisualScroll();
});
if(toLoad != null){
load(toLoad);
}
}
void add(LStatement statement){
statements.addChild(new StatementElem(statement));
}
String save(){
Seq<LStatement> st = statements.getChildren().<StatementElem>as().map(s -> s.st);
st.each(LStatement::saveUI);
return LAssembler.write(st);
}
void load(String asm){
Seq<LStatement> statements = LAssembler.read(asm);
statements.truncate(LogicBlock.maxInstructions);
this.statements.clearChildren();
for(LStatement st : statements){
add(st);
}
for(LStatement st : statements){
st.setupUI();
}
this.statements.layout();
}
@Override
public void draw(){
postDraw.clear();
super.draw();
postDraw.each(Runnable::run);
}
public class DragLayout extends WidgetGroup{
float space = Scl.scl(10f), prefWidth, prefHeight;
Seq<Element> seq = new Seq<>();
int insertPosition = 0;
{
setTransform(true);
}
@Override
public void layout(){
float cy = 0;
seq.clear();
float totalHeight = getChildren().sumf(e -> e.getHeight() + space);
height = prefHeight = totalHeight;
width = prefWidth = Scl.scl(targetWidth);
//layout everything normally
for(int i = 0; i < getChildren().size; i++){
Element e = getChildren().get(i);
//ignore the dragged element
if(dragging == e) continue;
e.setSize(width, e.getPrefHeight());
e.setPosition(0, height - cy, Align.topLeft);
cy += e.getPrefHeight() + space;
seq.add(e);
}
//insert the dragged element if necessary
if(dragging != null){
//find real position of dragged element top
float realY = dragging.getY(Align.top) + dragging.translation.y;
insertPosition = 0;
for(int i = 0; i < seq.size; i++){
Element cur = seq.get(i);
//find fit point
if(realY < cur.y && (i == seq.size - 1 || realY > seq.get(i + 1).y)){
insertPosition = i + 1;
break;
}
}
float shiftAmount = dragging.getHeight() + space;
//shift elements below insertion point down
for(int i = insertPosition; i < seq.size; i++){
seq.get(i).y -= shiftAmount;
}
}
invalidateHierarchy();
}
@Override
public float getPrefWidth(){
return prefWidth;
}
@Override
public float getPrefHeight(){
return prefHeight;
}
@Override
public void draw(){
Draw.alpha(parentAlpha);
//draw selection box indicating placement position
if(dragging != null && insertPosition <= seq.size){
float shiftAmount = dragging.getHeight();
float lastX = x;
float lastY = insertPosition == 0 ? height + y : seq.get(insertPosition - 1).y + y - space;
Tex.pane.draw(lastX, lastY - shiftAmount, width, dragging.getHeight());
}
super.draw();
}
void finishLayout(){
if(dragging != null){
//reset translation first
for(Element child : getChildren()){
child.setTranslation(0, 0);
}
clearChildren();
//reorder things
for(int i = 0; i <= insertPosition - 1 && i < seq.size; i++){
addChild(seq.get(i));
}
addChild(dragging);
for(int i = insertPosition; i < seq.size; i++){
addChild(seq.get(i));
}
dragging = null;
}
layout();
}
}
public class StatementElem extends Table{
LStatement st;
public StatementElem(LStatement st){
this.st = st;
st.elem = this;
background(Tex.whitePane);
setColor(st.category().color);
margin(0f);
touchable = Touchable.enabled;
table(Tex.whiteui, t -> {
t.color.set(color);
t.addListener(new HandCursorListener());
t.margin(6f);
t.touchable = Touchable.enabled;
t.add(st.name()).style(Styles.outlineLabel).color(color).padRight(8);
t.add().growX();
t.button(Icon.copy, Styles.logici, () -> {
}).padRight(6).get().tapped(() -> copy());
t.button(Icon.cancel, Styles.logici, () -> {
remove();
dragging = null;
statements.layout();
});
t.addListener(new InputListener(){
float lastx, lasty;
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
if(button == KeyCode.mouseMiddle){
copy();
return false;
}
Vec2 v = localToStageCoordinates(Tmp.v1.set(x, y));
lastx = v.x;
lasty = v.y;
dragging = StatementElem.this;
toFront();
statements.layout();
return true;
}
@Override
public void touchDragged(InputEvent event, float x, float y, int pointer){
Vec2 v = localToStageCoordinates(Tmp.v1.set(x, y));
translation.add(v.x - lastx, v.y - lasty);
lastx = v.x;
lasty = v.y;
statements.layout();
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
statements.finishLayout();
}
});
}).growX();
row();
table(t -> {
t.left();
t.marginLeft(4);
t.setColor(color);
st.build(t);
}).pad(4).padTop(2).left().grow();
marginBottom(7);
}
void copy(){
LStatement copy = st.copy();
if(copy != null){
StatementElem s = new StatementElem(copy);
statements.addChildAfter(StatementElem.this,s);
statements.layout();
copy.elem = s;
copy.setupUI();
}
}
@Override
public void draw(){
float pad = 5f;
Fill.dropShadow(x + width/2f, y + height/2f, width + pad, height + pad, 10f, 0.9f * parentAlpha);
Draw.color(0, 0, 0, 0.3f * parentAlpha);
Fill.crect(x, y, width, height);
Draw.reset();
super.draw();
}
}
public static class JumpButton extends ImageButton{
@NonNull Prov<StatementElem> to;
boolean selecting;
float mx, my;
public JumpButton(Color color, @NonNull Prov<StatementElem> getter, Cons<StatementElem> setter){
super(Tex.logicNode, Styles.colori);
to = getter;
getStyle().imageUpColor = color;
addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode code){
selecting = true;
setter.get(null);
mx = x;
my = y;
return true;
}
@Override
public void touchDragged(InputEvent event, float x, float y, int pointer){
mx = x;
my = y;
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode code){
localToStageCoordinates(Tmp.v1.set(x, y));
StatementElem elem = hovered();
if(elem != null && !isDescendantOf(elem)){
setter.get(elem);
}else{
setter.get(null);
}
selecting = false;
}
});
update(() -> {
if(to.get() != null && to.get().parent == null){
setter.get(null);
}
});
}
@Override
public void draw(){
super.draw();
postDraw.add(() -> {
Element hover = to.get() == null && selecting ? hovered() : to.get();
float tx = 0, ty = 0;
boolean draw = false;
//capture coordinates for use in lambda
float rx = x + translation.x, ry = y + translation.y;
Element p = parent;
while(p != null){
rx += p.x + p.translation.x;
ry += p.y + p.translation.y;
p = p.parent;
}
if(hover != null){
tx = hover.getX(Align.right) + hover.translation.x;
ty = hover.getY(Align.right) + hover.translation.y;
Element op = hover.parent;
while(op != null){
tx += op.x + op.translation.x;
ty += op.y + op.translation.y;
op = op.parent;
}
draw = true;
}else if(selecting){
tx = rx + mx;
ty = ry + my;
draw = true;
}
if(draw){
drawCurve(rx + width/2f, ry + height/2f, tx, ty, color);
float s = width;
Tex.logicNode.draw(tx + s*0.75f, ty - s/2f, -s, s);
}
});
}
StatementElem hovered(){
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(e != null){
while(e != null && !(e instanceof StatementElem)){
e = e.parent;
}
}
if(e == null || isDescendantOf(e)) return null;
return (StatementElem)e;
}
void drawCurve(float x, float y, float x2, float y2, Color color){
Lines.stroke(4f, color);
Draw.alpha(parentAlpha);
float dist = 100f;
Lines.curve(
x, y,
x + dist, y,
x2 + dist, y2,
x2, y2,
Math.max(20, (int)(Mathf.dst(x, y, x2, y2) / 5))
);
Draw.reset();
}
}
}

Some files were not shown because too many files have changed in this diff Show More