This commit is contained in:
J-VdS
2020-09-22 18:41:24 +02:00
249 changed files with 11578 additions and 10652 deletions

View File

@@ -63,6 +63,7 @@ public class BaseAI{
int range = 150;
Position pos = randomPosition();
//when there are no random positions, do nothing.
if(pos == null) return;
@@ -159,7 +160,10 @@ public class BaseAI{
private void tryWalls(){
Block wall = Blocks.copperWall;
Tile spawn = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core().tile : data.team.core().tile;
Building spawnt = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core() : data.team.core();
Tile spawn = spawnt == null ? null : spawnt.tile;
if(spawn == null) return;
for(int wx = lastX; wx <= lastX + lastW; wx++){
for(int wy = lastY; wy <= lastY + lastH; wy++){

View File

@@ -185,7 +185,7 @@ public class BlockIndexer{
if(other == null) continue;
if(other.team() == team && pred.get(other) && intSet.add(other.pos())){
if(other.team == team && pred.get(other) && intSet.add(other.pos())){
cons.get(other);
any = true;
}
@@ -212,11 +212,11 @@ public class BlockIndexer{
}
public void notifyTileDamaged(Building entity){
if(damagedTiles[entity.team().id] == null){
damagedTiles[entity.team().id] = new BuildingArray();
if(damagedTiles[entity.team.id] == null){
damagedTiles[entity.team.id] = new BuildingArray();
}
damagedTiles[entity.team().id].add(entity);
damagedTiles[entity.team.id].add(entity);
}
public Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
@@ -251,7 +251,7 @@ public class BlockIndexer{
if(e == null) continue;
if(e.team() != team || !pred.get(e) || !e.block().targetable)
if(e.team != team || !pred.get(e) || !e.block().targetable)
continue;
float ndst = e.dst2(x, y);
@@ -390,7 +390,7 @@ public class BlockIndexer{
for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){
Building result = world.build(x, y);
//when a targetable block is found, mark this quadrant as occupied and stop searching
if(result != null && result.team() == team){
if(result != null && result.team == team){
bits.set(quadrantX, quadrantY);
break outer;
}

View File

@@ -99,7 +99,7 @@ public class WaveSpawner{
private void eachFlyerSpawn(Floatc2 cons){
for(Tile tile : spawns){
float angle = Angles.angle(tile.x, tile.y, world.width() / 2, world.height() / 2);
float angle = Angles.angle(world.width() / 2, world.height() / 2, tile.x, tile.y);
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);

View File

@@ -6,6 +6,8 @@ import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class FlyingAI extends AIController{
@Override
@@ -18,7 +20,7 @@ public class FlyingAI extends AIController{
unit.wobble();
}
if(target != null && unit.hasWeapons()){
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
if(unit.type().weapons.first().rotate){
moveTo(target, unit.range() * 0.8f);
unit.lookAt(target);
@@ -26,6 +28,15 @@ public class FlyingAI extends AIController{
attack(80f);
}
}
if(target == null && command() == UnitCommand.attack && state.rules.waves && unit.team == state.rules.defaultTeam){
moveTo(getClosestSpawner(), state.rules.dropZoneRadius + 120f);
}
if(command() == UnitCommand.rally){
target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
moveTo(target, 60f);
}
}
@Override

View File

@@ -1,31 +1,55 @@
package mindustry.ai.types;
import arc.math.*;
import mindustry.ai.Pathfinder.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.pathfinder;
import java.util.*;
import static mindustry.Vars.*;
public class GroundAI extends AIController{
//static final float commandCooldown = 60f * 10;
//float commandTimer = 60*3;
@Override
public void updateMovement(){
Building core = unit.closestEnemyCore();
if(core != null){
if(unit.within(core,unit.range() / 1.1f)){
target = core;
if(core != null && unit.within(core, unit.range() / 1.1f + core.block.size * tilesize / 2f)){
target = core;
Arrays.fill(targets, core);
}
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
boolean move = true;
if(state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
}
if(!unit.within(core, unit.range() * 0.5f)){
moveToCore(FlagTarget.enemyCores);
if(move) moveToCore(FlagTarget.enemyCores);
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
moveToCore(FlagTarget.rallyPoints);
}
}
if(unit.type().canBoost){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
}
if(!Units.invalidateTarget(target, unit, unit.range())){
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
@@ -33,12 +57,27 @@ public class GroundAI extends AIController{
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
//auto-command works but it's very buggy
/*
if(unit instanceof Commanderc){
Commanderc c = (Commanderc)unit;
//try to command when missing members
if(c.controlling().size <= unit.type().commandLimit/2){
commandTimer -= Time.delta;
if(commandTimer <= 0){
c.commandNearby(new SquareFormation(), u -> !(u.controller() instanceof FormationAI) && !(u instanceof Commanderc));
commandTimer = commandCooldown;
}
}
}*/
}
protected void moveToCore(FlagTarget path){
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, unit.team(), path);
Tile targetTile = pathfinder.getTargetTile(tile, unit.team, path);
if(tile == targetTile) return;

View File

@@ -39,7 +39,7 @@ public class SuicideAI extends GroundAI{
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
Tile tile = Vars.world.tile(x, y);
if(tile != null && tile.build == target) return false;
if(tile != null && tile.build != null && tile.build.team() != unit.team()){
if(tile != null && tile.build != null && tile.build.team != unit.team()){
blockedByBlock = true;
return true;
}else{

View File

@@ -1,6 +1,5 @@
package mindustry.async;
import arc.*;
import arc.box2d.*;
import arc.box2d.BodyDef.*;
import arc.math.geom.*;
@@ -98,7 +97,7 @@ public class PhysicsProcess implements AsyncProcess{
ref.lastVelocity.set(ref.velocity);
}
physics.step(Core.graphics.getDeltaTime(), 5, 8);
physics.step(1f/45f, 5, 8);
//get delta vectors
for(PhysicRef ref : refs){

View File

@@ -19,6 +19,7 @@ public class MusicControl{
public Seq<Music> ambientMusic = Seq.with();
/** darker music, used in times of conflict */
public Seq<Music> darkMusic = Seq.with();
protected Music lastRandomPlayed;
protected Interval timer = new Interval();
protected @Nullable Music current;

View File

@@ -19,7 +19,6 @@ 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.*;
@@ -78,12 +77,13 @@ public class Blocks implements ContentList{
duo, scatter, scorch, hail, arc, wave, lancer, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown, segment, parallax,
//units
commandCenter,
groundFactory, airFactory, navalFactory,
additiveReconstructor, multiplicativeReconstructor, exponentialReconstructor, tetrativeReconstructor,
repairPoint, resupplyPoint,
//logic
message, switchBlock, microProcessor, logicProcessor, logicDisplay, memoryCell,
message, switchBlock, microProcessor, logicProcessor, hyperProcessor, logicDisplay, memoryCell,
//campaign
launchPad, launchPadLarge,
@@ -270,7 +270,7 @@ public class Blocks implements ContentList{
}};
grass = new Floor("grass"){{
attributes.set(Attribute.water, 0.1f);
}};
salt = new Floor("salt"){{
@@ -908,6 +908,7 @@ public class Blocks implements ContentList{
health = 45;
speed = 0.03f;
displayedSpeed = 4.2f;
buildCostMultiplier = 2f;
}};
titaniumConveyor = new Conveyor("titanium-conveyor"){{
@@ -995,12 +996,14 @@ public class Blocks implements ContentList{
consumes.power(1.75f);
}};
payloadConveyor = new PayloadConveyor("mass-conveyor"){{
requirements(Category.distribution, with(Items.copper, 1));
payloadConveyor = new PayloadConveyor("payload-conveyor"){{
requirements(Category.distribution, with(Items.graphite, 10, Items.copper, 20));
canOverdrive = false;
}};
payloadRouter = new PayloadRouter("payload-router"){{
requirements(Category.distribution, with(Items.copper, 1));
requirements(Category.distribution, with(Items.graphite, 15, Items.copper, 20));
canOverdrive = false;
}};
//endregion
@@ -1338,7 +1341,7 @@ public class Blocks implements ContentList{
unloader = new Unloader("unloader"){{
requirements(Category.effect, with(Items.titanium, 25, Items.silicon, 30));
speed = 7f;
speed = 6f;
}};
//endregion
@@ -1485,7 +1488,7 @@ public class Blocks implements ContentList{
reloadTime = 35f;
shootCone = 40f;
rotatespeed = 8f;
powerUse = 5f;
powerUse = 4f;
targetAir = false;
range = 90f;
shootEffect = Fx.lightningShoot;
@@ -1501,8 +1504,8 @@ public class Blocks implements ContentList{
hasPower = true;
size = 2;
force = 2.5f;
scaledForce = 5f;
force = 3f;
scaledForce = 5.5f;
range = 170f;
damage = 0.08f;
health = 160 * size * size;
@@ -1555,14 +1558,14 @@ public class Blocks implements ContentList{
}};
segment = new PointDefenseTurret("segment"){{
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 50));
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 25));
hasPower = true;
consumes.power(3f);
size = 2;
shootLength = 5f;
bulletDamage = 12f;
reloadTime = 25f;
reloadTime = 20f;
health = 190 * size * size;
}};
@@ -1698,11 +1701,17 @@ public class Blocks implements ContentList{
//endregion
//region units
commandCenter = new CommandCenter("command-center"){{
requirements(Category.units, ItemStack.with(Items.copper, 200, Items.lead, 250, Items.silicon, 250, Items.graphite, 100));
size = 2;
health = size * size * 55;
}};
groundFactory = new UnitFactory("ground-factory"){{
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.coal, 20)),
new UnitPlan(UnitTypes.dagger, 60f * 15, with(Items.silicon, 10, Items.lead, 10)),
new UnitPlan(UnitTypes.crawler, 60f * 12, 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;
@@ -1710,7 +1719,7 @@ public class Blocks implements ContentList{
}};
airFactory = new UnitFactory("air-factory"){{
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
requirements(Category.units, with(Items.copper, 60, Items.lead, 70));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 15)),
new UnitPlan(UnitTypes.mono, 60f * 35, with(Items.silicon, 30, Items.lead, 15)),
@@ -1720,7 +1729,7 @@ public class Blocks implements ContentList{
}};
navalFactory = new UnitFactory("naval-factory"){{
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
requirements(Category.units, with(Items.copper, 150, Items.lead, 130, Items.metaglass, 120));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.risso, 60f * 30f, with(Items.silicon, 20, Items.metaglass, 25)),
};
@@ -1768,11 +1777,11 @@ public class Blocks implements ContentList{
}};
exponentialReconstructor = new Reconstructor("exponential-reconstructor"){{
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 750, Items.titanium, 950, Items.thorium, 450, Items.plastanium, 350, Items.phasefabric, 250));
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 750, Items.titanium, 950, Items.thorium, 450, Items.plastanium, 350, Items.phasefabric, 450));
size = 7;
consumes.power(12f);
consumes.items(with(Items.silicon, 250, Items.titanium, 500, Items.plastanium, 400));
consumes.power(13f);
consumes.items(with(Items.silicon, 450, Items.titanium, 550, Items.plastanium, 550));
consumes.liquid(Liquids.cryofluid, 1f);
constructTime = 60f * 60f * 1.5f;
@@ -1780,15 +1789,16 @@ public class Blocks implements ContentList{
upgrades = new UnitType[][]{
{UnitTypes.zenith, UnitTypes.antumbra},
{UnitTypes.spiroct, UnitTypes.arkyid},
};
}};
tetrativeReconstructor = new Reconstructor("tetrative-reconstructor"){{
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 1500, Items.thorium, 500, Items.plastanium, 50, Items.phasefabric, 600, Items.surgealloy, 500));
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 1500, Items.thorium, 500, Items.plastanium, 450, Items.phasefabric, 600, Items.surgealloy, 500));
size = 9;
consumes.power(25f);
consumes.items(with(Items.silicon, 350, Items.plastanium, 450, Items.surgealloy, 400, Items.phasefabric, 150));
consumes.items(with(Items.silicon, 350, Items.plastanium, 550, Items.surgealloy, 350, Items.phasefabric, 150));
consumes.liquid(Liquids.cryofluid, 3f);
constructTime = 60f * 60f * 4;
@@ -1861,7 +1871,6 @@ public class Blocks implements ContentList{
//looked up by name, no ref needed
new LegacyMechPad("legacy-mech-pad");
new LegacyUnitFactory("legacy-unit-factory");
new LegacyCommandCenter("legacy-command-center");
//endregion
//region campaign
@@ -1897,7 +1906,7 @@ public class Blocks implements ContentList{
}};
microProcessor = new LogicBlock("micro-processor"){{
requirements(Category.logic, with(Items.copper, 80, Items.lead, 50, Items.silicon, 60));
requirements(Category.logic, with(Items.copper, 80, Items.lead, 50, Items.silicon, 50));
instructionsPerTick = 2;
@@ -1905,15 +1914,28 @@ public class Blocks implements ContentList{
}};
logicProcessor = new LogicBlock("logic-processor"){{
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 140, Items.graphite, 80, Items.thorium, 70));
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 100, Items.graphite, 60, Items.thorium, 50));
instructionsPerTick = 5;
range = 16 * 10;
range = 8 * 20;
size = 2;
}};
hyperProcessor = new LogicBlock("hyper-processor"){{
requirements(Category.logic, with(Items.lead, 450, Items.silicon, 150, Items.thorium, 75, Items.surgealloy, 50));
consumes.liquid(Liquids.cryofluid, 0.08f);
hasLiquids = true;
instructionsPerTick = 25;
range = 8 * 40;
size = 3;
}};
logicDisplay = new LogicDisplay("logic-display"){{
requirements(Category.logic, with(Items.copper, 200, Items.lead, 120, Items.silicon, 100, Items.metaglass, 50));

View File

@@ -163,7 +163,7 @@ public class Bullets implements ContentList{
width = 6f;
height = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 20f;
splashDamage = 22f;
splashDamageRadius = 20f;
fragBullet = flakGlassFrag;
fragBullets = 5;
@@ -240,7 +240,7 @@ public class Bullets implements ContentList{
explodeRange = 20f;
}};
missileExplosive = new MissileBulletType(3f, 10){{
missileExplosive = new MissileBulletType(3.7f, 10){{
width = 8f;
height = 8f;
shrinkY = 0f;
@@ -255,7 +255,7 @@ public class Bullets implements ContentList{
statusDuration = 60f;
}};
missileIncendiary = new MissileBulletType(3f, 12){{
missileIncendiary = new MissileBulletType(3.7f, 12){{
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
width = 7f;
@@ -269,7 +269,7 @@ public class Bullets implements ContentList{
status = StatusEffects.burning;
}};
missileSurge = new MissileBulletType(3f, 20){{
missileSurge = new MissileBulletType(3.7f, 20){{
width = 8f;
height = 8f;
shrinkY = 0f;
@@ -279,7 +279,7 @@ public class Bullets implements ContentList{
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
lightning = 2;
lightningLength = 14;
lightningLength = 10;
}};
standardCopper = new BasicBulletType(2.5f, 9){{

View File

@@ -33,8 +33,10 @@ public class Fx{
float scl = 1f + e.fout() * 2f;
UnitType unit = e.data();
rect(unit.region, e.x, e.y,
unit.region.getWidth() * Draw.scl * scl, unit.region.getHeight() * Draw.scl * scl, 180f);
TextureRegion region = unit.icon(Cicon.full);
rect(region, e.x, e.y,
region.getWidth() * Draw.scl * scl, region.getHeight() * Draw.scl * scl, 180f);
}),
@@ -512,6 +514,29 @@ public class Fx{
}),
sapExplosion = new Effect(25, e -> {
color(Pal.sapBullet);
e.scaled(6, i -> {
stroke(3f * i.fout());
Lines.circle(e.x, e.y, 3f + i.fin() * 80f);
});
color(Color.gray);
randLenVectors(e.id, 9, 2f + 70 * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f);
});
color(Pal.sapBulletBack);
stroke(1f * e.fout());
randLenVectors(e.id + 1, 8, 1f + 60f * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
});
}),
massiveExplosion = new Effect(30, e -> {
color(Pal.missileYellow);

View File

@@ -17,7 +17,7 @@ import static mindustry.content.UnitTypes.*;
import static mindustry.type.ItemStack.*;
public class TechTree implements ContentList{
private static ObjectMap<UnlockableContent, TechNode> map = new ObjectMap<>();
static ObjectMap<UnlockableContent, TechNode> map = new ObjectMap<>();
public static Seq<TechNode> all;
public static TechNode root;
@@ -38,7 +38,6 @@ public class TechTree implements ContentList{
node(distributor);
node(sorter, () -> {
node(invertedSorter);
node(message);
node(overflowGate, () -> {
node(underflowGate);
});
@@ -204,6 +203,26 @@ public class TechTree implements ContentList{
});
});
});
node(microProcessor, () -> {
node(switchBlock, () -> {
node(message, () -> {
node(logicDisplay, () -> {
});
node(memoryCell, () -> {
});
});
node(logicProcessor, () -> {
node(hyperProcessor, () -> {
});
});
});
});
});
});
});
@@ -340,6 +359,10 @@ public class TechTree implements ContentList{
});
node(groundFactory, () -> {
node(commandCenter, () -> {
});
node(dagger, () -> {
node(mace, () -> {
node(fortress, () -> {
@@ -406,8 +429,6 @@ public class TechTree implements ContentList{
});
});
//TODO research sectors
node(groundZero, () -> {
node(frozenForest, Seq.with(
new SectorComplete(groundZero),
@@ -544,7 +565,7 @@ public class TechTree implements ContentList{
}
public static class TechNode{
private static TechNode context;
static TechNode context;
/** Depth in tech tree. */
public int depth;
@@ -556,25 +577,20 @@ public class TechTree implements ContentList{
public ItemStack[] requirements;
/** Requirements that have been fulfilled. Always the same length as the requirement array. */
public final ItemStack[] finishedRequirements;
/** Extra objectives needed to research this. TODO implement */
/** Extra objectives needed to research this. */
public Seq<Objective> objectives = new Seq<>();
/** Time required to research this content, in seconds. */
public float time;
/** Nodes that depend on this node. */
public final Seq<TechNode> children = new Seq<>();
/** Research progress, in seconds. */
public float progress;
TechNode(@Nullable TechNode ccontext, UnlockableContent content, ItemStack[] requirements, Runnable children){
if(ccontext != null){
ccontext.children.add(this);
}
if(ccontext != null) ccontext.children.add(this);
this.parent = ccontext;
this.content = content;
this.requirements = requirements;
this.depth = parent == null ? 0 : parent.depth + 1;
this.progress = Core.settings == null ? 0 : Core.settings.getFloat("research-" + content.name, 0f);
this.time = Seq.with(requirements).mapFloat(i -> i.item.cost * i.amount).sum() * 10;
this.finishedRequirements = new ItemStack[requirements.length];
@@ -599,7 +615,6 @@ public class TechTree implements ContentList{
/** Flushes research progress to settings. */
public void save(){
Core.settings.put("research-" + content.name, progress);
//save finished requirements by item type
for(ItemStack stack : finishedRequirements){

View File

@@ -27,7 +27,7 @@ public class UnitTypes implements ContentList{
public static @EntityDef({Unitc.class, Legsc.class}) UnitType atrax;
//legs + building
public static @EntityDef({Unitc.class, Legsc.class, Builderc.class}) UnitType spiroct, arkyid;
public static @EntityDef({Unitc.class, Legsc.class, Builderc.class}) UnitType spiroct, arkyid, toxopid;
//air (no special traits)
public static @EntityDef({Unitc.class}) UnitType flare, eclipse, horizon, zenith, antumbra;
@@ -72,7 +72,6 @@ public class UnitTypes implements ContentList{
mace = new UnitType("mace"){{
speed = 0.4f;
hitsize = 9f;
range = 10f;
health = 500;
armor = 4f;
@@ -84,12 +83,11 @@ public class UnitTypes implements ContentList{
reload = 14f;
recoil = 1f;
ejectEffect = Fx.none;
bullet = new BulletType(3f, 30f){{
bullet = new BulletType(3.9f, 30f){{
ammoMultiplier = 3f;
hitSize = 7f;
lifetime = 42f;
lifetime = 12f;
pierce = true;
drag = 0.05f;
statusDuration = 60f * 4;
shootEffect = Fx.shootSmallFlame;
hitEffect = Fx.hitFlameSmall;
@@ -165,7 +163,7 @@ public class UnitTypes implements ContentList{
boostMultiplier = 1.5f;
speed = 0.48f;
hitsize = 10f;
health = 300f;
health = 320f;
buildSpeed = 0.9f;
armor = 4f;
@@ -173,7 +171,7 @@ public class UnitTypes implements ContentList{
mineSpeed = 5f;
commandLimit = 8;
abilities.add(new ShieldFieldAbility(15f, 30f, 60f * 5, 60f));
abilities.add(new ShieldFieldAbility(20f, 40f, 60f * 5, 60f));
weapons.add(new Weapon("heal-shotgun-weapon"){{
x = 5f;
@@ -193,7 +191,7 @@ public class UnitTypes implements ContentList{
bullet = new LightningBulletType(){{
lightningColor = hitColor = Pal.heal;
damage = 12f;
damage = 15f;
lightningLength = 7;
lightningLengthRand = 7;
shootEffect = Fx.shootHeal;
@@ -206,7 +204,7 @@ public class UnitTypes implements ContentList{
hitsize = 12f;
boostMultiplier = 2f;
itemCapacity = 80;
health = 640f;
health = 650f;
buildSpeed = 1.7f;
canBoost = true;
armor = 9f;
@@ -219,7 +217,7 @@ public class UnitTypes implements ContentList{
mineSpeed = 7f;
drawShields = false;
abilities.add(new ForceFieldAbility(60f, 0.12f, 200f, 60f * 8));
abilities.add(new ForceFieldAbility(60f, 0.2f, 300f, 60f * 7));
weapons.add(new Weapon("beam-weapon"){{
shake = 2f;
@@ -230,7 +228,7 @@ public class UnitTypes implements ContentList{
shootSound = Sounds.laser;
bullet = new LaserBulletType(){{
damage = 27f;
damage = 30f;
recoil = 1f;
sideAngle = 45f;
sideWidth = 1f;
@@ -316,7 +314,7 @@ public class UnitTypes implements ContentList{
drag = 0.4f;
hitsize = 12f;
rotateSpeed = 3f;
health = 600;
health = 760;
immunities = ObjectSet.with(StatusEffects.burning, StatusEffects.melting);
legCount = 6;
legLength = 13f;
@@ -352,7 +350,7 @@ public class UnitTypes implements ContentList{
despawnEffect = Fx.none;
width = 0.54f;
lifetime = 35f;
knockback = -1.2f;
knockback = -1.24f;
}};
}});
@@ -365,62 +363,109 @@ public class UnitTypes implements ContentList{
bullet = new SapBulletType(){{
sapStrength = 0.65f;
length = 40f;
damage = 12;
damage = 13;
shootEffect = Fx.shootSmall;
hitColor = color = Color.valueOf("bf92f9");
despawnEffect = Fx.none;
width = 0.4f;
lifetime = 25f;
knockback = -0.6f;
knockback = -0.65f;
}};
}});
}};
//TODO implement
arkyid = new UnitType("arkyid"){{
drag = 0.1f;
speed = 0.5f;
hitsize = 9f;
health = 140;
hitsize = 21f;
health = 8000;
armor = 6f;
legCount = 6;
legMoveSpace = 1f;
legPairOffset = 3;
legLength = 34f;
rotateShooting = false;
legLength = 30f;
legExtension = -15;
legBaseOffset = 10f;
landShake = 2f;
landShake = 1f;
legSpeed = 0.1f;
legLengthScl = 1f;
rippleScale = 2f;
legSpeed = 0.2f;
legSplashDamage = 32;
legSplashRange = 30;
hovering = true;
allowLegStep = true;
visualElevation = 0.4f;
groundLayer = Layer.legUnit;
BulletType sapper = new SapBulletType(){{
sapStrength = 0.8f;
length = 55f;
damage = 34;
shootEffect = Fx.shootSmall;
hitColor = color = Color.valueOf("bf92f9");
despawnEffect = Fx.none;
width = 0.55f;
lifetime = 30f;
knockback = -1f;
}};
weapons.add(
new Weapon("missiles-mount"){{
reload = 20f;
new Weapon("spiroct-weapon"){{
reload = 9f;
x = 4f;
y = 8f;
rotate = true;
shake = 1f;
bullet = new MissileBulletType(2.7f, 12, "missile"){{
width = 8f;
height = 8f;
shrinkY = 0f;
drag = -0.003f;
homingRange = 60f;
keepVelocity = false;
splashDamageRadius = 25f;
splashDamage = 10f;
lifetime = 120f;
trailColor = Color.gray;
backColor = Pal.bulletYellowBack;
frontColor = Pal.bulletYellow;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
weaveScale = 8f;
weaveMag = 2f;
bullet = sapper;
}},
new Weapon("spiroct-weapon"){{
reload = 15f;
x = 9f;
y = 6f;
rotate = true;
bullet = sapper;
}},
new Weapon("spiroct-weapon"){{
reload = 23f;
x = 14f;
y = 0f;
rotate = true;
bullet = sapper;
}},
new Weapon("large-purple-mount"){{
y = -7f;
x = 9f;
shootY = 7f;
reload = 45;
shake = 3f;
rotateSpeed = 2f;
ejectEffect = Fx.shellEjectSmall;
shootSound = Sounds.shootBig;
rotate = true;
occlusion = 8f;
recoil = 3f;
bullet = new ArtilleryBulletType(2f, 12){{
hitEffect = Fx.sapExplosion;
knockback = 0.8f;
lifetime = 70f;
width = height = 19f;
collidesTiles = false;
ammoMultiplier = 4f;
splashDamageRadius = 95f;
splashDamage = 55f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
lightning = 3;
lightningLength = 10;
smokeEffect = Fx.shootBigSmoke2;
shake = 5f;
status = StatusEffects.sapped;
statusDuration = 60f * 10;
}};
}});
}};
@@ -448,7 +493,7 @@ public class UnitTypes implements ContentList{
}};
horizon = new UnitType("horizon"){{
health = 300;
health = 350;
speed = 2f;
accel = 0.08f;
drag = 0.016f;
@@ -457,19 +502,19 @@ public class UnitTypes implements ContentList{
engineOffset = 7.8f;
range = 140f;
faceTarget = false;
armor = 2f;
armor = 4f;
weapons.add(new Weapon(){{
minShootVelocity = 0.75f;
x = 3f;
shootY = 0f;
reload = 12f;
reload = 11f;
shootCone = 180f;
ejectEffect = Fx.none;
inaccuracy = 15f;
ignoreRotation = true;
shootSound = Sounds.none;
bullet = new BombBulletType(23f, 25f){{
bullet = new BombBulletType(28f, 25f){{
width = 10f;
height = 14f;
hitEffect = Fx.flakExplosion;
@@ -483,21 +528,21 @@ public class UnitTypes implements ContentList{
}};
zenith = new UnitType("zenith"){{
health = 1000;
speed = 1.9f;
health = 700;
speed = 1.7f;
accel = 0.04f;
drag = 0.016f;
flying = true;
range = 140f;
hitsize = 18f;
lowAltitude = true;
armor = 6f;
armor = 5f;
engineOffset = 12f;
engineSize = 3f;
weapons.add(new Weapon("zenith-missiles"){{
reload = 32f;
reload = 40f;
x = 7f;
rotate = true;
shake = 1f;
@@ -533,21 +578,21 @@ public class UnitTypes implements ContentList{
rotateSpeed = 1.9f;
flying = true;
lowAltitude = true;
health = 9000;
health = 7000;
armor = 9f;
engineOffset = 21;
engineSize = 5.3f;
hitsize = 58f;
hitsize = 56f;
BulletType missiles = new MissileBulletType(2.7f, 10){{
width = 8f;
height = 8f;
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 40f;
splashDamage = 40f;
splashDamageRadius = 20f;
splashDamage = 30f;
ammoMultiplier = 4f;
lifetime = 80f;
lifetime = 50f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
@@ -581,17 +626,18 @@ public class UnitTypes implements ContentList{
new Weapon("large-bullet-mount"){{
y = 2f;
x = 10f;
shootY = 12f;
reload = 10;
shootY = 10f;
reload = 12;
shake = 1f;
rotateSpeed = 2f;
ejectEffect = Fx.shellEjectSmall;
shootSound = Sounds.shootBig;
rotate = true;
occlusion = 8f;
bullet = new BasicBulletType(7f, 60){{
bullet = new BasicBulletType(7f, 50){{
width = 12f;
height = 18f;
lifetime = 25f;
shootEffect = Fx.shootBig;
}};
}}
@@ -612,6 +658,18 @@ public class UnitTypes implements ContentList{
destructibleWreck = false;
armor = 13f;
BulletType fragBullet = new FlakBulletType(4f, 5){{
shootEffect = Fx.shootBig;
ammoMultiplier = 4f;
splashDamage = 42f;
splashDamageRadius = 25f;
collidesGround = true;
lifetime = 38f;
status = StatusEffects.blasted;
statusDuration = 60f;
}};
weapons.add(
new Weapon("large-laser-mount"){{
shake = 4f;
@@ -619,14 +677,14 @@ public class UnitTypes implements ContentList{
x = 18f;
y = 5f;
rotateSpeed = 2f;
reload = 50f;
reload = 45f;
recoil = 4f;
shootSound = Sounds.laser;
occlusion = 20f;
rotate = true;
bullet = new LaserBulletType(){{
damage = 75f;
damage = 90f;
sideAngle = 20f;
sideWidth = 1.5f;
sideLength = 80f;
@@ -636,50 +694,29 @@ public class UnitTypes implements ContentList{
colors = new Color[]{Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
}};
}},
new Weapon("missiles-mount"){{
new Weapon("large-artillery"){{
x = 11f;
y = 27f;
rotateSpeed = 2f;
reload = 4f;
reload = 9f;
shootSound = Sounds.flame;
occlusion = 7f;
rotate = true;
recoil = 0.5f;
bullet = Bullets.pyraFlame;
bullet = fragBullet;
}},
new Weapon("large-artillery"){{
y = -13f;
x = 20f;
reload = 18f;
reload = 12f;
ejectEffect = Fx.shellEjectSmall;
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;
}};
bullet = fragBullet;
}});
}};
@@ -1001,7 +1038,7 @@ public class UnitTypes implements ContentList{
lifetime = 60f;
shootEffect = Fx.shootSmall;
smokeEffect = Fx.shootSmallSmoke;
tileDamageMultiplier = 0.95f;
tileDamageMultiplier = 0.09f;
}};
}});
}};
@@ -1092,6 +1129,7 @@ public class UnitTypes implements ContentList{
hitsize = 0f;
health = 1;
rotateSpeed = 360f;
itemCapacity = 0;
}
@Override

View File

@@ -243,11 +243,11 @@ public class ContentLoader{
}
public Block block(int id){
return (Block)getByID(ContentType.block, id);
return getByID(ContentType.block, id);
}
public Block block(String name){
return (Block)getByName(ContentType.block, name);
return getByName(ContentType.block, name);
}
public Seq<Item> items(){
@@ -255,7 +255,7 @@ public class ContentLoader{
}
public Item item(int id){
return (Item)getByID(ContentType.item, id);
return getByID(ContentType.item, id);
}
public Seq<Liquid> liquids(){
@@ -263,7 +263,7 @@ public class ContentLoader{
}
public Liquid liquid(int id){
return (Liquid)getByID(ContentType.liquid, id);
return getByID(ContentType.liquid, id);
}
public Seq<BulletType> bullets(){
@@ -271,7 +271,7 @@ public class ContentLoader{
}
public BulletType bullet(int id){
return (BulletType)getByID(ContentType.bullet, id);
return getByID(ContentType.bullet, id);
}
public Seq<SectorPreset> sectors(){

View File

@@ -155,12 +155,6 @@ public class Control implements ApplicationListener, Loadable{
}
});
Events.on(ZoneRequireCompleteEvent.class, e -> {
if(e.objective.display() != null){
ui.hudfrag.showToast(Core.bundle.format("zone.requirement.complete", e.zoneForMet.localizedName, e.objective.display()));
}
});
//delete save on campaign game over
Events.on(GameOverEvent.class, e -> {
if(state.isCampaign() && !net.client() && !headless){

View File

@@ -302,6 +302,11 @@ public class Logic implements ApplicationListener{
});
}
@Remote(called = Loc.both)
public static void updateGameOver(Team winner){
state.gameOver = true;
}
@Remote(called = Loc.both)
public static void gameOver(Team winner){
state.stats.wavesLasted = state.wave;
@@ -320,6 +325,10 @@ public class Logic implements ApplicationListener{
Events.fire(Trigger.update);
universe.updateGlobal();
if(Core.settings.modified() && !state.isPlaying()){
Core.settings.forceSave();
}
if(state.isGame()){
if(!net.client()){
state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type().isCounted);

View File

@@ -366,6 +366,9 @@ public class NetClient implements ApplicationListener{
@Remote
public static void playerDisconnect(int playerid){
if(netClient != null){
netClient.addRemovedEntity(playerid);
}
Groups.player.removeByID(playerid);
}
@@ -437,13 +440,14 @@ public class NetClient implements ApplicationListener{
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, short coreDataLen, byte[] coreData){
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, boolean gameOver, short coreDataLen, byte[] coreData){
try{
if(wave > state.wave){
state.wave = wave;
Events.fire(new WaveEvent());
}
state.gameOver = gameOver;
state.wavetime = waveTime;
state.wave = wave;
state.enemies = enemies;
@@ -558,6 +562,22 @@ public class NetClient implements ApplicationListener{
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.builder().plans().size, 10);
int totalLength = 0;
//prevent buffer overflow by checking config length
for(int i = 0; i < usedRequests; i++){
BuildPlan plan = player.builder().plans().get(i);
if(plan.config instanceof byte[]){
int length = ((byte[])plan.config).length;
totalLength += length;
}
if(totalLength > 2048){
usedRequests = i + 1;
break;
}
}
requests = new BuildPlan[usedRequests];
for(int i = 0; i < usedRequests; i++){
requests[i] = player.builder().plans().get(i);

View File

@@ -34,8 +34,9 @@ import static arc.util.Log.*;
import static mindustry.Vars.*;
public class NetServer implements ApplicationListener{
private static final int maxSnapshotSize = 430, timerBlockSync = 0;
private static final float serverSyncTime = 12, blockSyncTime = 60 * 8;
/** note that snapshots are compressed, so the max snapshot size here is above the typical UDP safe limit */
private static final int maxSnapshotSize = 800, timerBlockSync = 0;
private static final float serverSyncTime = 12, blockSyncTime = 60 * 6;
private static final FloatBuffer fbuffer = FloatBuffer.allocate(20);
private static final Vec2 vector = new Vec2();
private static final Rect viewport = new Rect();
@@ -370,6 +371,11 @@ public class NetServer implements ApplicationListener{
return;
}
if(currentlyKicking[0] != null){
player.sendMessage("[scarlet]A vote is already in progress.");
return;
}
if(args.length == 0){
StringBuilder builder = new StringBuilder();
builder.append("[orange]Players to kick: \n");
@@ -384,9 +390,7 @@ public class NetServer implements ApplicationListener{
int id = Strings.parseInt(args[0].substring(1));
found = Groups.player.find(p -> p.id() == id);
}else{
found = Groups.player.find(p -> {
return p.name.equalsIgnoreCase(args[0]);
});
found = Groups.player.find(p -> p.name.equalsIgnoreCase(args[0]));
}
if(found != null){
@@ -526,6 +530,10 @@ public class NetServer implements ApplicationListener{
public static void serverPacketUnreliable(Player player, String type, String contents){
serverPacketReliable(player, type, contents);
}
private static boolean invalid(float f){
return Float.isInfinite(f) || Float.isNaN(f);
}
@Remote(targets = Loc.client, unreliable = true)
public static void clientSnapshot(
@@ -544,6 +552,16 @@ public class NetServer implements ApplicationListener{
NetConnection con = player.con;
if(con == null || snapshotID < con.lastReceivedClientSnapshot) return;
//validate coordinates just in case
if(invalid(x)) x = 0f;
if(invalid(y)) y = 0f;
if(invalid(xVelocity)) xVelocity = 0f;
if(invalid(yVelocity)) yVelocity = 0f;
if(invalid(pointerX)) pointerX = 0f;
if(invalid(pointerY)) pointerY = 0f;
if(invalid(rotation)) rotation = 0f;
if(invalid(baseRotation)) baseRotation = 0f;
boolean verifyPosition = !player.dead() && netServer.admins.getStrict() && headless;
if(con.lastReceivedClientTime == 0) con.lastReceivedClientTime = Time.millis() - 16;
@@ -612,12 +630,12 @@ public class NetServer implements ApplicationListener{
if(!player.dead()){
Unit unit = player.unit();
unit.vel.set(xVelocity, yVelocity).limit(unit.type().speed);
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
float maxSpeed = (boosting ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
float maxSpeed = ((player.unit().type().canBoost && player.unit().isFlying()) ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
if(unit.isGrounded()){
maxSpeed *= unit.floorSpeedMultiplier();
}
unit.vel.set(xVelocity, yVelocity).limit(maxSpeed);
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
if(con.lastUnit != unit){
@@ -681,8 +699,8 @@ public class NetServer implements ApplicationListener{
public static void adminRequest(Player player, Player other, AdminAction action){
if(!player.admin){
Log.warn("ACCESS DENIED: Player @ / @ attempted to perform admin action without proper security access.",
player.name, player.con.address);
Log.warn("ACCESS DENIED: Player @ / @ attempted to perform admin action '@' on '@' without proper security access.",
player.name, player.con.address, action.name(), other == null ? null : other.name);
return;
}
@@ -735,7 +753,7 @@ public class NetServer implements ApplicationListener{
}
public boolean isWaitingForPlayers(){
if(state.rules.pvp){
if(state.rules.pvp && !state.gameOver){
int used = 0;
for(TeamData t : state.teams.getActive()){
if(Groups.player.count(p -> p.team() == t.team) > 0){
@@ -761,6 +779,10 @@ public class NetServer implements ApplicationListener{
}
if(state.isGame() && net.server()){
if(state.rules.pvp){
state.serverPaused = isWaitingForPlayers();
}
sync();
}
}
@@ -794,7 +816,7 @@ public class NetServer implements ApplicationListener{
if(!entity.block().sync) continue;
sent ++;
dataStream.writeInt(entity.tile().pos());
dataStream.writeInt(entity.pos());
entity.writeAll(Writes.get(dataStream));
if(syncStream.size() > maxSnapshotSize){
@@ -828,7 +850,7 @@ public class NetServer implements ApplicationListener{
byte[] stateBytes = syncStream.toByteArray();
//write basic state data.
Call.stateSnapshot(player.con, state.wavetime, state.wave, state.enemies, state.serverPaused, (short)stateBytes.length, net.compressSnapshot(stateBytes));
Call.stateSnapshot(player.con, state.wavetime, state.wave, state.enemies, state.serverPaused, state.gameOver, (short)stateBytes.length, net.compressSnapshot(stateBytes));
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);

View File

@@ -185,6 +185,8 @@ public class Renderer implements ApplicationListener{
}
public void draw(){
Events.fire(Trigger.preDraw);
camera.update();
if(Float.isNaN(camera.position.x) || Float.isNaN(camera.position.y)){
@@ -205,6 +207,8 @@ public class Renderer implements ApplicationListener{
Draw.sort(true);
Events.fire(Trigger.draw);
if(pixelator.enabled()){
pixelator.register();
}
@@ -254,6 +258,8 @@ public class Renderer implements ApplicationListener{
Draw.reset();
Draw.flush();
Draw.sort(false);
Events.fire(Trigger.postDraw);
}
private void drawBackground(){

View File

@@ -128,6 +128,8 @@ public class UI implements ApplicationListener, Loadable{
public void update(){
if(disableUI || Core.scene == null) return;
Events.fire(Trigger.uiDrawBegin);
Core.scene.act();
Core.scene.draw();
@@ -143,6 +145,8 @@ public class UI implements ApplicationListener, Loadable{
control.tutorial.draw();
Draw.flush();
}
Events.fire(Trigger.uiDrawEnd);
}
@Override
@@ -221,12 +225,15 @@ public class UI implements ApplicationListener, Loadable{
}
public TextureRegionDrawable getIcon(String name){
if(Icon.icons.containsKey(name)){
return Icon.icons.get(name);
}
if(Icon.icons.containsKey(name)) return Icon.icons.get(name);
return Core.atlas.getDrawable("error");
}
public TextureRegionDrawable getIcon(String name, String def){
if(Icon.icons.containsKey(name)) return Icon.icons.get(name);
return getIcon(def);
}
public void loadAnd(Runnable call){
loadAnd("@loading", call);
}

View File

@@ -484,6 +484,9 @@ public class World{
private class Context implements WorldContext{
Context(){
}
@Override
public Tile tile(int index){
return tiles.geti(index);

View File

@@ -37,18 +37,18 @@ public class MapGenerateDialog extends BaseDialog{
private Pixmap pixmap;
private Texture texture;
private GenerateInput input = new GenerateInput();
private Seq<GenerateFilter> filters = new Seq<>();
Seq<GenerateFilter> filters = new Seq<>();
private int scaling = mobile ? 3 : 1;
private Table filterTable;
private AsyncExecutor executor = new AsyncExecutor(1);
private AsyncResult<Void> result;
private boolean generating;
boolean generating;
private GenTile returnTile = new GenTile();
private GenTile[][] buffer1, buffer2;
private Cons<Seq<GenerateFilter>> applier;
private CachedTile ctile = new CachedTile(){
CachedTile ctile = new CachedTile(){
//nothing.
@Override
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
@@ -178,7 +178,7 @@ public class MapGenerateDialog extends BaseDialog{
add(new Image(Styles.black8));
add(new Image(Icon.refresh, Scaling.none));
visible(() -> generating && !updateEditorOnChange);
}}).grow().padRight(10);
}}).uniformX().grow().padRight(10);
t.pane(p -> filterTable = p.marginRight(6)).update(pane -> {
if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){
return;
@@ -191,7 +191,7 @@ public class MapGenerateDialog extends BaseDialog{
}else{
Core.scene.setScrollFocus(null);
}
}).grow().get().setScrollingDisabled(true, false);
}).grow().uniformX().get().setScrollingDisabled(true, false);
}).grow();
buffer1 = create();
@@ -213,7 +213,7 @@ public class MapGenerateDialog extends BaseDialog{
}
void rebuildFilters(){
int cols = Math.max((int)(Math.max(filterTable.parent.getWidth(), Core.graphics.getWidth()/2f * 0.9f) / Scl.scl(290f)), 1);
int cols = Math.max((int)(Core.graphics.getWidth()/2f / Scl.scl(290f)), 1);
filterTable.clearChildren();
filterTable.top().left();
int i = 0;
@@ -229,7 +229,7 @@ public class MapGenerateDialog extends BaseDialog{
t.setColor(Pal.gray);
t.top().left();
t.add(filter.name()).left().padLeft(6);
t.add(filter.name()).left().padLeft(6).width(100f).wrap();
t.add().growX();
@@ -408,6 +408,9 @@ public class MapGenerateDialog extends BaseDialog{
public byte team;
public short block, floor, ore;
GenTile(){
}
public void set(Block floor, Block wall, Block ore, Team team){
this.floor = floor.id;
this.block = wall.id;

View File

@@ -22,7 +22,7 @@ import static mindustry.Vars.ui;
public class MapView extends Element implements GestureListener{
private MapEditor editor;
private EditorTool tool = EditorTool.pencil;
EditorTool tool = EditorTool.pencil;
private float offsetx, offsety;
private float zoom = 1f;
private boolean grid = false;
@@ -31,11 +31,11 @@ public class MapView extends Element implements GestureListener{
private Rect rect = new Rect();
private Vec2[][] brushPolygons = new Vec2[MapEditor.brushSizes.length][0];
private boolean drawing;
private int lastx, lasty;
private int startx, starty;
private float mousex, mousey;
private EditorTool lastTool;
boolean drawing;
int lastx, lasty;
int startx, starty;
float mousex, mousey;
EditorTool lastTool;
public MapView(MapEditor editor){
this.editor = editor;
@@ -204,7 +204,7 @@ public class MapView extends Element implements GestureListener{
zoom = Mathf.clamp(zoom, 0.2f, 20f);
}
private Point2 project(float x, float y){
Point2 project(float x, float y){
float ratio = 1f / ((float)editor.width() / editor.height());
float size = Math.min(width, height);
float sclwidth = size * zoom;

View File

@@ -23,7 +23,7 @@ import static mindustry.game.SpawnGroup.*;
public class WaveInfoDialog extends BaseDialog{
private int displayed = 20;
private Seq<SpawnGroup> groups = new Seq<>();
Seq<SpawnGroup> groups = new Seq<>();
private Table table;
private int start = 0;

View File

@@ -30,24 +30,26 @@ public class Damage{
private static Unit tmpUnit;
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color, boolean damage){
if(damage){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
}
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
for(int i = 0; i < waves; i++){
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
for(int i = 0; i < waves; i++){
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
}
}
if(explosiveness > 15f){
@@ -87,7 +89,7 @@ public class Damage{
tr.trns(angle, length);
Intc2 collider = (cx, cy) -> {
Building tile = world.build(cx, cy);
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team() != team && tile.collide(hitter)){
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team != team && tile.collide(hitter)){
tile.collision(hitter);
collidedBlocks.add(tile.pos());
hitter.type.hit(hitter, tile.x, tile.y);

View File

@@ -101,7 +101,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
T t = map.get(id);
if(t != null){ //remove if present in map already
remove(t);
t.remove();
}
}

View File

@@ -17,7 +17,7 @@ public class Fires{
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
public static void create(Tile tile){
if(net.client() || tile == null) return; //not clientside.
if(net.client() || tile == null || !state.rules.fire) return; //not clientside.
Fire fire = map.get(tile.pos());

View File

@@ -19,8 +19,17 @@ public class Units{
private static boolean boolResult;
@Remote(called = Loc.server)
public static void unitDeath(Unit unit){
unit.killed();
public static void unitDeath(int uid){
Unit unit = Groups.unit.getByID(uid);
//if there's no unit don't add it later and get it stuck as a ghost
if(netClient != null){
netClient.addRemovedEntity(uid);
}
if(unit != null){
unit.killed();
}
}
@Remote(called = Loc.server)

View File

@@ -31,6 +31,7 @@ public class ForceFieldAbility implements Ability{
//break shield
if(paramUnit.shield <= trait.damage()){
paramUnit.shield -= cooldown * regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, radius, paramUnit.team.color);
}

View File

@@ -92,6 +92,7 @@ public abstract class BulletType extends Content{
public float homingPower = 0f;
public float homingRange = 50f;
public Color lightningColor = Pal.surge;
public int lightning;
public int lightningLength = 5;
/** Use a negative value to use default bullet damage. */
@@ -172,7 +173,7 @@ public abstract class BulletType extends Content{
}
for(int i = 0; i < lightning; i++){
Lightning.create(b, Pal.surge, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength);
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength);
}
}
@@ -264,7 +265,7 @@ public abstract class BulletType extends Content{
bullet.damage = damage < 0 ? this.damage : damage;
bullet.add();
if(keepVelocity && owner instanceof Hitboxc) bullet.vel.add(((Hitboxc)owner).deltaX(), ((Hitboxc)owner).deltaY());
if(keepVelocity && owner instanceof Hitboxc) bullet.vel.add(((Hitboxc)owner).deltaX() / Time.delta, ((Hitboxc)owner).deltaY() / Time.delta);
return bullet;
}

View File

@@ -29,7 +29,7 @@ public class HealBulletType extends BulletType{
@Override
public boolean collides(Bullet b, Building tile){
return tile.team() != b.team || tile.healthf() < 1f;
return tile.team != b.team || tile.healthf() < 1f;
}
@Override
@@ -46,7 +46,7 @@ public class HealBulletType extends BulletType{
public void hitTile(Bullet b, Building tile){
super.hit(b);
if(tile.team() == b.team && !(tile.block() instanceof BuildBlock)){
if(tile.team == b.team && !(tile.block() instanceof BuildBlock)){
Fx.healBlockFull.at(tile.x, tile.y, tile.block().size, Pal.heal);
tile.heal(healPercent / 100f * tile.maxHealth());
}

View File

@@ -27,7 +27,7 @@ abstract class BlockUnitComp implements Unitc{
@Override
public void update(){
if(tile != null){
team = tile.team();
team = tile.team;
}
}
@@ -61,7 +61,7 @@ abstract class BlockUnitComp implements Unitc{
public void team(Team team){
if(tile != null && this.team != team){
this.team = team;
if(tile.team() != team){
if(tile.team != team){
tile.team(team);
}
}

View File

@@ -144,7 +144,6 @@ abstract class BuilderComp implements Unitc{
boolean shouldSkip(BuildPlan request, @Nullable Building core){
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || team().rules().infiniteResources || request.breaking || core == null) return false;
//TODO these are bad criteria
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item)) && !request.initialized);
}

View File

@@ -41,7 +41,7 @@ import static mindustry.Vars.*;
@Component(base = true)
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 float timeToSleep = 60f * 1, timeToUncontrol = 60f * 6;
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
static final Seq<Building> tempTileEnts = new Seq<>();
static final Seq<Tile> tempTiles = new Seq<>();
@@ -57,6 +57,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient byte dump;
transient int rotation;
transient boolean enabled = true;
transient float enabledControlTime;
PowerModule power;
ItemModule items;
@@ -134,8 +135,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public final void writeBase(Writes write){
write.f(health);
write.b(rotation);
write.b(rotation | 0b10000000);
write.b(team.id);
write.b(0); //extra padding for later use
if(items != null) items.write(write);
if(power != null) power.write(write);
if(liquids != null) liquids.write(write);
@@ -144,12 +146,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public final void readBase(Reads read){
health = read.f();
rotation = read.b();
byte rot = read.b();
team = Team.get(read.b());
if(items != null) items.read(read);
if(power != null) power.read(read);
if(liquids != null) liquids.read(read);
if(cons != null) cons.read(read);
rotation = rot & 0b01111111;
boolean legacy = true;
if((rot & 0b10000000) != 0){
read.b(); //padding
legacy = false;
}
if(items != null) items.read(read, legacy);
if(power != null) power.read(read, legacy);
if(liquids != null) liquids.read(read, legacy);
if(cons != null) cons.read(read, legacy);
}
public void writeAll(Writes write){
@@ -330,15 +340,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region handler methods
/** Called when this block is dropped as a payload. */
public void dropped(){
}
/** This is for logic blocks. */
public void handleString(Object value){
}
public void created(){}
public boolean shouldConsume(){
return true;
return enabled;
}
public boolean productionValid(){
@@ -404,7 +419,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
int trns = block.size/2 + 1;
Tile next = tile.getNearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
if(next != null && next.build != null && next.build.team() == team && next.build.acceptPayload(base(), todump)){
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(base(), todump)){
next.build.handlePayload(base(), todump);
return true;
}
@@ -425,7 +440,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptPayload(base(), todump)){
if(other.team == team && other.acceptPayload(base(), todump)){
other.handlePayload(base(), todump);
incrementDump(proximity.size);
return true;
@@ -504,34 +519,31 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
next = next.getLiquidDestination(base(), liquid);
if(next.team() == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
if(next.team == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
float ofract = next.liquids.get(liquid) / next.block.liquidCapacity;
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
flow = Math.min(flow, next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f);
if(next.acceptLiquid(base(), liquid, 0f)){
float ofract = next.liquids().get(liquid) / next.block.liquidCapacity;
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
flow = Math.min(flow, next.block.liquidCapacity - next.liquids().get(liquid) - 0.001f);
if(flow > 0f && ofract <= fract && next.acceptLiquid(base(), liquid, flow)){
next.handleLiquid(base(), liquid, flow);
liquids.remove(liquid, flow);
return flow;
}else if(next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){
//TODO these are incorrect effect positions
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
if(flow > 0f && ofract <= fract && next.acceptLiquid(base(), liquid, flow)){
next.handleLiquid(base(), liquid, flow);
liquids.remove(liquid, flow);
return flow;
}else if(ofract > 0.1f && fract > 0.1f){
//TODO these are incorrect effect positions
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
Liquid other = next.liquids().current();
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
damage(1 * Time.delta);
next.damage(1 * Time.delta);
if(Mathf.chance(0.1 * Time.delta)){
Fx.fire.at(fx, fy);
}
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
if(Mathf.chance(0.2f * Time.delta)){
Fx.steam.at(fx, fy);
}
Liquid other = next.liquids.current();
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
damage(1 * Time.delta);
next.damage(1 * Time.delta);
if(Mathf.chance(0.1 * Time.delta)){
Fx.fire.at(fx, fy);
}
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
if(Mathf.chance(0.2f * Time.delta)){
Fx.steam.at(fx, fy);
}
}
}
@@ -562,7 +574,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
if(other.team == team && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
return;
}
@@ -580,7 +592,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
if(other.team == team && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
return true;
}
@@ -613,7 +625,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int ii = 0; ii < content.items().size; ii++){
Item item = content.item(ii);
if(other.team() == team && items.has(item) && other.acceptItem(base(), item) && canDump(other, item)){
if(other.team == team && items.has(item) && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
items.remove(item, 1);
incrementDump(proximity.size);
@@ -621,7 +633,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
}else{
if(other.team() == team && other.acceptItem(base(), todump) && canDump(other, todump)){
if(other.team == team && other.acceptItem(base(), todump) && canDump(other, todump)){
other.handleItem(base(), todump);
items.remove(todump, 1);
incrementDump(proximity.size);
@@ -647,7 +659,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Try offloading an item to a nearby container in its facing direction. Returns true if success. */
public boolean moveForward(Item item){
Building other = front();
if(other != null && other.team() == team && other.acceptItem(base(), item)){
if(other != null && other.team == team && other.acceptItem(base(), item)){
other.handleItem(base(), item);
return true;
}
@@ -751,6 +763,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void drawSelect(){
}
public void drawDisabled(){
Draw.color(Color.scarlet);
Draw.alpha(0.8f);
float size = 6f;
Draw.rect(Icon.cancel.getRegion(), x, y, size, size);
Draw.reset();
}
public void draw(){
Draw.rect(block.region, x, y, block.rotate ? rotdeg() : 0);
@@ -871,7 +893,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
power += this.power.status * block.consumes.getPower().capacity;
}
if(block.hasLiquids){
if(block.hasLiquids && state.rules.damageExplosions){
liquids.each((liquid, amount) -> {
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
@@ -887,7 +909,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
});
}
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame);
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame, state.rules.damageExplosions);
if(!floor().solid && !floor().isLiquid){
Effect.rubble(x, y, block.size);
}
@@ -1203,6 +1226,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void control(LAccess type, double p1, double p2, double p3, double p4){
if(type == LAccess.enabled){
enabled = !Mathf.zero((float)p1);
enabledControlTime = timeToUncontrol;
}
}
@@ -1230,6 +1254,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
timeScale = 1f;
}
if(block.autoResetEnabled){
enabledControlTime -= Time.delta;
if(enabledControlTime <= 0){
enabled = true;
}
}
if(sound != null){
sound.update(x, y, shouldActiveSound());
}

View File

@@ -1,10 +1,13 @@
package mindustry.entities.comp;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.ai.formations.*;
import mindustry.ai.types.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -12,6 +15,7 @@ import mindustry.gen.*;
@Component
abstract class CommanderComp implements Unitc{
private static final Seq<FormationMember> members = new Seq<>();
private static final Seq<Unit> units = new Seq<>();
@Import float x, y, rotation;
@@ -44,6 +48,28 @@ abstract class CommanderComp implements Unitc{
clearCommand();
}
void commandNearby(FormationPattern pattern){
commandNearby(pattern, u -> true);
}
void commandNearby(FormationPattern pattern, Boolf<Unit> include){
Formation formation = new Formation(new Vec3(x, y, rotation), pattern);
formation.slotAssignmentStrategy = new DistanceAssignmentStrategy(pattern);
units.clear();
Units.nearby(team(), x, y, 200f, u -> {
if(u.isAI() && include.get(u) && u != base()){
units.add(u);
}
});
units.sort(u -> u.dst2(this));
units.truncate(type().commandLimit);
command(formation, units);
}
void command(Formation formation, Seq<Unit> units){
clearCommand();

View File

@@ -15,14 +15,14 @@ import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component(base = true)
abstract class FireComp implements Timedc, Posc, Firec{
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
private static final float spreadChance = 0.05f, fireballChance = 0.07f;
@Import float time, lifetime, x, y;
Tile tile;
private Block block;
private float baseFlammability = -1, puddleFlammability;
private transient Block block;
private transient float baseFlammability = -1, puddleFlammability;
@Override
public void update(){
@@ -99,4 +99,9 @@ abstract class FireComp implements Timedc, Posc, Firec{
public void afterRead(){
Fires.register(base());
}
@Override
public void afterSync(){
Fires.register(base());
}
}

View File

@@ -14,7 +14,7 @@ import static mindustry.Vars.net;
abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
@Import float x, y;
@Import float x, y, speedMultiplier;
@Import Vec2 vel;
@SyncLocal float elevation;
@@ -56,7 +56,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
float floorSpeedMultiplier(){
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
return on.speedMultiplier;
return on.speedMultiplier * speedMultiplier;
}
@Override

View File

@@ -13,7 +13,7 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati
@Override
public void update(){
float len = deltaLen();
baseRotation = Angles.moveToward(baseRotation, deltaAngle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed));
walkTime += Time.delta *len;
baseRotation = Angles.moveToward(baseRotation, deltaAngle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed / Time.delta) * Time.delta);
walkTime += len;
}
}

View File

@@ -95,7 +95,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
Building tile = payload.entity;
int tx = Vars.world.toTile(x - tile.block().offset), ty = Vars.world.toTile(y - tile.block().offset);
Tile on = Vars.world.tile(tx, ty);
if(on != null && Build.validPlace(tile.block(), tile.team(), tx, ty, tile.rotation)){
if(on != null && Build.validPlace(tile.block(), tile.team, tx, ty, tile.rotation)){
int rot = (int)((rotation + 45f) / 90f) % 4;
payload.place(on, rot);

View File

@@ -31,6 +31,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health;
@Import boolean dead;
@Import Team team;
@Import int id;
private UnitController controller;
private UnitType type;
@@ -223,7 +224,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//apply knockback based on spawns
if(team != state.rules.waveTeam){
float relativeSize = state.rules.dropZoneRadius + bounds()/2f + 1f;
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
for(Tile spawn : spawner.getSpawns()){
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
vel().add(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta));
@@ -275,10 +276,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
damageContinuous(floor.damageTaken);
}
if(!net.client() && tile.solid()){
if(tile.solid()){
if(type.canBoost){
elevation = 1f;
}else{
}else if(!net.client()){
kill();
}
}
@@ -314,7 +315,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
public void destroy(){
float explosiveness = 2f + item().explosiveness * stack().amount;
float flammability = item().flammability * stack().amount;
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, Pal.darkFlame);
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, Pal.darkFlame, state.rules.damageExplosions);
float shake = hitSize / 3f;
@@ -389,6 +390,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(dead || net.client()) return;
//deaths are synced; this calls killed()
Call.unitDeath(base());
Call.unitDeath(id);
}
}

View File

@@ -1,5 +1,6 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@@ -18,7 +19,7 @@ abstract class VelComp implements Posc{
@Override
public void update(){
move(vel.x * Time.delta, vel.y * Time.delta);
vel.scl(1f - drag * Time.delta);
vel.scl(Mathf.clamp(1f - drag * Time.delta));
}
boolean moving(){

View File

@@ -62,8 +62,8 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
Draw.z(Layer.debris);
Floor floor = floorOn();
Color color = Tmp.c1.set(floor.mapColor).mul(1.5f);
Floor floor = tileOn() == null ? Blocks.air.asFloor() : tileOn().floor();
Color color = Tmp.c1.set(floor.mapColor.equals(Color.black) ? Blocks.water.mapColor : floor.mapColor).mul(1.5f);
trailColor.lerp(color, Mathf.clamp(Time.delta * 0.04f));
tleft.draw(trailColor, type.trailScl);

View File

@@ -113,7 +113,8 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
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
//TODO checking for velocity this way isn't entirely correct
(vel.len() >= mount.weapon.minShootVelocity || (net.active() && !isLocal())) && //check velocity requirements
mount.reload <= 0.0001f && //reload has to be 0
Angles.within(weapon.rotate ? mount.rotation : this.rotation, mount.targetRotation, mount.weapon.shootCone) //has to be within the cone
){

View File

@@ -2,7 +2,9 @@ package mindustry.entities.units;
import arc.math.*;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
@@ -33,6 +35,10 @@ public class AIController implements UnitController{
updateMovement();
}
protected UnitCommand command(){
return unit.team.data().command;
}
protected void updateMovement(){
}
@@ -115,6 +121,10 @@ public class AIController implements UnitController{
}
protected @Nullable Tile getClosestSpawner(){
return Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns());
}
protected void circle(Position target, float circleLength){
circle(target, circleLength, unit.type().speed);
}

View File

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

View File

@@ -35,6 +35,12 @@ public class DefaultWaves{
max = 4;
}},
new SpawnGroup(UnitTypes.pulsar){{
begin = 13;
spacing = 3;
unitScaling = 0.5f;
}},
new SpawnGroup(UnitTypes.mace){{
begin = 7;
spacing = 3;

View File

@@ -30,7 +30,12 @@ public class EventType{
openWiki,
teamCoreDamage,
socketConfigChanged,
update
update,
draw,
preDraw,
postDraw,
uiDrawBegin,
uiDrawEnd
}
public static class WinEvent{}
@@ -77,7 +82,6 @@ public class EventType{
}
}
public static class CommandIssueEvent{
public final Building tile;
public final UnitCommand command;
@@ -106,27 +110,6 @@ public class EventType{
}
}
/** Called when a zone's requirements are met. */
public static class ZoneRequireCompleteEvent{
public final SectorPreset zoneMet, zoneForMet;
public final Objectives.Objective objective;
public ZoneRequireCompleteEvent(SectorPreset zoneMet, SectorPreset zoneForMet, Objectives.Objective objective){
this.zoneMet = zoneMet;
this.zoneForMet = zoneForMet;
this.objective = objective;
}
}
/** Called when a zone's requirements are met. */
public static class ZoneConfigureCompleteEvent{
public final SectorPreset zone;
public ZoneConfigureCompleteEvent(SectorPreset zone){
this.zone = zone;
}
}
/** Called when a sector is conquered, e.g. a boss or base is defeated. */
public static class SectorCaptureEvent{
public final Sector sector;

View File

@@ -40,6 +40,15 @@ public class Rules{
public boolean reactorExplosions = true;
/** Whether schematics are allowed */
public boolean schematicAllowed = true;
<<<<<<< HEAD
=======
/** Whether copying is allowed */
public boolean copyAllowed = true;
/** Whether friendly explosions can occur and set fire/damage other blocks. */
public boolean damageExplosions = true;
/** Whether fire is enabled. */
public boolean fire = true;
>>>>>>> fee15caf7aa42f112eebe4c465885b6c37fd51a5
/** Whether units use and require ammo. */
public boolean unitAmmo = false;
/** How fast unit pads build units. */

View File

@@ -23,14 +23,14 @@ import java.util.*;
import static mindustry.Vars.*;
public class Saves{
private Seq<SaveSlot> saves = new Seq<>();
private @Nullable SaveSlot current;
Seq<SaveSlot> saves = new Seq<>();
@Nullable SaveSlot current;
private @Nullable SaveSlot lastSectorSave;
private AsyncExecutor previewExecutor = new AsyncExecutor(1);
AsyncExecutor previewExecutor = new AsyncExecutor(1);
private boolean saving;
private float time;
private long totalPlaytime;
long totalPlaytime;
private long lastTimestamp;
public Saves(){

View File

@@ -102,7 +102,7 @@ public class Teams{
}
public void registerCore(CoreBuild core){
TeamData data = get(core.team());
TeamData data = get(core.team);
//add core if not present
if(!data.cores.contains(core)){
data.cores.add(core);
@@ -117,7 +117,7 @@ public class Teams{
}
public void unregisterCore(CoreBuild entity){
TeamData data = get(entity.team());
TeamData data = get(entity.team);
//remove core
data.cores.remove(entity);
//unregister in active list
@@ -181,7 +181,7 @@ public class Teams{
/** @return whether this team is controlled by the AI and builds bases. */
public boolean hasAI(){
return state.rules.attackMode && team.rules().ai;
return team.rules().ai;
}
@Override

View File

@@ -23,9 +23,9 @@ public class Tutorial{
private static final int mineCopper = 18;
private static final int blocksToBreak = 3, blockOffset = -6;
private ObjectSet<String> events = new ObjectSet<>();
private ObjectIntMap<Block> blocksPlaced = new ObjectIntMap<>();
private int sentence;
ObjectSet<String> events = new ObjectSet<>();
ObjectIntMap<Block> blocksPlaced = new ObjectIntMap<>();
int sentence;
public TutorialStage stage = TutorialStage.values()[0];
public Tutorial(){

View File

@@ -140,7 +140,7 @@ public class Universe{
sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed);
//check if the sector has been attacked too many times...
if(sector.hasBase() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){
if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){
//fire event for losing the sector
Events.fire(new SectorLoseEvent(sector));

View File

@@ -249,7 +249,7 @@ public class BlockRenderer implements Disposable{
Draw.z(Layer.block);
}
if(entity.team() != player.team()){
if(entity.team != player.team()){
entity.drawTeam();
Draw.z(Layer.block);
}

View File

@@ -256,5 +256,8 @@ public class FloorRenderer implements Disposable{
/** Maps cache layer ID to cache ID in the batch.
* -1 means that this cache is unoccupied. */
int[] caches = new int[CacheLayer.all.length];
Chunk(){
}
}
}

View File

@@ -5,6 +5,9 @@ public class Layer{
public static final float
//min layer
min = -11,
//background, which may be planets or an image or nothing at all
background = -10,
@@ -81,7 +84,10 @@ public class Layer{
end = 200,
//things after pixelation - used for text
endPixeled = 210
endPixeled = 210,
//max layer
max = 220
;
}

View File

@@ -111,7 +111,7 @@ public class OverlayRenderer{
if(dst < state.rules.enemyCoreBuildRadius * 2.2f){
Draw.color(Color.darkGray);
Lines.circle(core.x, core.y - 2, state.rules.enemyCoreBuildRadius);
Draw.color(Pal.accent, core.team().color, 0.5f + Mathf.absin(Time.time(), 10f, 0.5f));
Draw.color(Pal.accent, core.team.color, 0.5f + Mathf.absin(Time.time(), 10f, 0.5f));
Lines.circle(core.x, core.y, state.rules.enemyCoreBuildRadius);
}
});
@@ -134,8 +134,11 @@ public class OverlayRenderer{
Vec2 vec = Core.input.mouseWorld(input.getMouseX(), input.getMouseY());
Building tile = world.buildWorld(vec.x, vec.y);
if(tile != null && tile.team() == player.team()){
if(tile != null && tile.team == player.team()){
tile.drawSelect();
if(!tile.enabled && tile.block.drawDisabled){
tile.drawDisabled();
}
if(Core.input.keyDown(Binding.rotateplaced) && tile.block().rotate && tile.interactable(player.team())){
control.input.drawArrow(tile.block(), tile.tileX(), tile.tileY(), tile.rotation, true);

View File

@@ -9,6 +9,8 @@ public class Pal{
command = Color.valueOf("eab678"),
sap = Color.valueOf("665c9f"),
sapBullet = Color.valueOf("bf92f9"),
sapBulletBack = Color.valueOf("6d56bf"),
spore = Color.valueOf("7457ce"),

View File

@@ -8,7 +8,8 @@ import arc.input.*;
public enum Binding implements KeyBind{
move_x(new Axis(KeyCode.a, KeyCode.d), "general"),
move_y(new Axis(KeyCode.s, KeyCode.w)),
mouse_move(KeyCode.mouseForward),
mouse_move(KeyCode.mouseBack),
pan(KeyCode.mouseForward),
boost(KeyCode.shiftLeft),
control(KeyCode.controlLeft),

View File

@@ -27,24 +27,35 @@ import static mindustry.Vars.*;
import static mindustry.input.PlaceMode.*;
public class DesktopInput extends InputHandler{
private Vec2 movement = new Vec2();
public Vec2 movement = new Vec2();
/** Current cursor type. */
private Cursor cursorType = SystemCursor.arrow;
public Cursor cursorType = SystemCursor.arrow;
/** Position where the player started dragging a line. */
private int selectX, selectY, schemX, schemY;
public int selectX, selectY, schemX, schemY;
/** Last known line positions.*/
private int lastLineX, lastLineY, schematicX, schematicY;
public int lastLineX, lastLineY, schematicX, schematicY;
/** Whether selecting mode is active. */
private PlaceMode mode;
public PlaceMode mode;
/** Animation scale for line. */
private float selectScale;
public float selectScale;
/** Selected build request for movement. */
private @Nullable BuildPlan sreq;
public @Nullable BuildPlan sreq;
/** Whether player is currently deleting removal requests. */
private boolean deleting = false, shouldShoot = false;
public boolean deleting = false, shouldShoot = false, panning = false;
/** Mouse pan speed. */
public float panScale = 0.005f, panSpeed = 4.5f, panBoostSpeed = 9f;
@Override
public void buildUI(Group group){
group.fill(t -> {
t.visible(() -> Core.settings.getBool("hints") && !player.dead() && !player.unit().spawnedByCore() && !(Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty()));
t.bottom();
t.table(Styles.black6, b -> {
b.defaults().left();
b.label(() -> Core.bundle.format("respawn", Core.keybinds.get(Binding.respawn).key.toString())).style(Styles.outlineLabel);
}).margin(6f);
});
group.fill(t -> {
t.bottom();
t.visible(() -> {
@@ -77,15 +88,6 @@ public class DesktopInput extends InputHandler{
});
}).margin(6f);
});
group.fill(t -> {
t.visible(() -> Core.settings.getBool("hints") && !player.dead() && !player.unit().spawnedByCore());
t.bottom();
t.table(Styles.black6, b -> {
b.defaults().left();
b.label(() -> Core.bundle.format("respawn", Core.keybinds.get(Binding.respawn).key.toString())).style(Styles.outlineLabel);
}).margin(6f);
});
}
@Override
@@ -132,14 +134,12 @@ public class DesktopInput extends InputHandler{
}
//draw schematic requests
for(BuildPlan request : selectRequests){
request.animScale = 1f;
drawRequest(request);
}
selectRequests.each(req -> {
req.animScale = 1f;
drawRequest(req);
});
for(BuildPlan request : selectRequests){
drawOverRequest(request);
}
selectRequests.each(this::drawOverRequest);
if(player.isBuilder()){
//draw things that may be placed soon
@@ -180,22 +180,35 @@ public class DesktopInput extends InputHandler{
ui.listfrag.toggle();
}
//TODO awful UI state checking code
if((player.dead() || state.isPaused()) && !ui.chatfrag.shown()){
if(!(scene.getKeyboardFocus() instanceof TextField) && !scene.hasDialog()){
//move camera around
float camSpeed = !Core.input.keyDown(Binding.boost) ? 3f : 8f;
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta * camSpeed));
boolean panCam = false;
float camSpeed = !Core.input.keyDown(Binding.boost) ? panSpeed : panBoostSpeed;
if(Core.input.keyDown(Binding.mouse_move)){
Core.camera.position.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * 0.005f, -1, 1) * camSpeed;
Core.camera.position.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * 0.005f, -1, 1) * camSpeed;
}
if(input.keyDown(Binding.pan)){
panCam = true;
panning = true;
}
if((Math.abs(Core.input.axis(Binding.move_x)) > 0 || Math.abs(Core.input.axis(Binding.move_y)) > 0 || input.keyDown(Binding.mouse_move)) && (!scene.hasField())){
panning = false;
}
//TODO awful UI state checking code
if(((player.dead() || state.isPaused()) && !ui.chatfrag.shown()) && (!scene.hasField() && !scene.hasDialog())){
if(input.keyDown(Binding.mouse_move)){
panCam = true;
}
}else if(!player.dead()){
panning = false;
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta * camSpeed));
}else if(!player.dead() && !panning){
Core.camera.position.lerpDelta(player, Core.settings.getBool("smoothcamera") ? 0.08f : 1f);
}
if(panCam){
Core.camera.position.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * panScale, -1, 1) * camSpeed;
Core.camera.position.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * panScale, -1, 1) * camSpeed;
}
shouldShoot = !scene.hasMouse();
if(!scene.hasMouse()){
@@ -327,22 +340,29 @@ public class DesktopInput extends InputHandler{
table.row();
table.left().margin(0f).defaults().size(48f).left();
//TODO localize these
table.button(Icon.paste, Styles.clearPartiali, () -> {
ui.schematics.show();
<<<<<<< HEAD
}).tooltip("Schematics");
=======
}).tooltip("@schematics").disabled(d -> !state.rules.schematicAllowed);
>>>>>>> fee15caf7aa42f112eebe4c465885b6c37fd51a5
table.button(Icon.tree, Styles.clearPartiali, () -> {
ui.research.show();
}).visible(() -> state.isCampaign()).tooltip("Research");
}).visible(() -> state.isCampaign()).tooltip("@research");
table.button(Icon.map, Styles.clearPartiali, () -> {
ui.planet.show();
}).visible(() -> state.isCampaign()).tooltip("Planet Map");
}).visible(() -> state.isCampaign()).tooltip("@planetmap");
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)).tooltip("Launch Core");
.disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements)).tooltip("@launchcore");
}
void pollInput(){
@@ -555,6 +575,8 @@ public class DesktopInput extends InputHandler{
@Override
public void updateState(){
super.updateState();
if(state.isMenu()){
droppingItem = false;
mode = none;
@@ -574,7 +596,7 @@ public class DesktopInput extends InputHandler{
//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;
baseSpeed = ((Commanderc)unit).minFormationSpeed() * 0.95f;
}
float speed = baseSpeed * Mathf.lerp(1f, unit.type().canBoost ? unit.type().boostMultiplier : 1f, unit.elevation) * strafePenalty;
@@ -593,8 +615,8 @@ public class DesktopInput extends InputHandler{
if(aimCursor){
unit.lookAt(mouseAngle);
}else{
if(unit.moving()){
unit.lookAt(unit.vel().angle());
if(!movement.isZero()){
unit.lookAt(unit.vel.isZero() ? movement.angle() : unit.vel.angle());
}
}
@@ -616,7 +638,6 @@ public class DesktopInput extends InputHandler{
//update payload input
if(unit instanceof Payloadc){
Payloadc pay = (Payloadc)unit;
if(Core.input.keyTap(Binding.pickupCargo)){
tryPickupPayload();

View File

@@ -14,7 +14,6 @@ import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.ai.formations.*;
import mindustry.ai.formations.patterns.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
@@ -63,13 +62,13 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public boolean isBuilding = true, buildWasAutoPaused = false;
public @Nullable UnitType controlledType;
protected @Nullable Schematic lastSchematic;
protected GestureDetector detector;
protected PlaceLine line = new PlaceLine();
protected BuildPlan resultreq;
protected BuildPlan brequest = new BuildPlan();
protected Seq<BuildPlan> lineRequests = new Seq<>();
protected Seq<BuildPlan> selectRequests = new Seq<>();
public @Nullable Schematic lastSchematic;
public GestureDetector detector;
public PlaceLine line = new PlaceLine();
public BuildPlan resultreq;
public BuildPlan brequest = new BuildPlan();
public Seq<BuildPlan> lineRequests = new Seq<>();
public Seq<BuildPlan> selectRequests = new Seq<>();
//methods to override
@@ -108,6 +107,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void pickupUnitPayload(Player player, Unit target){
if(player == null) return;
Unit unit = player.unit();
Payloadc pay = (Payloadc)unit;
@@ -120,10 +121,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void pickupBlockPayload(Player player, Building tile){
if(player == null) return;
Unit unit = player.unit();
Payloadc pay = (Payloadc)unit;
if(tile != null && tile.team() == unit.team && pay.payloads().size < unit.type().payloadCapacity
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 && tile.canPickup()){
@@ -143,6 +146,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void dropPayload(Player player, float x, float y){
if(player == null) return;
Payloadc pay = (Payloadc)player.unit();
//allow a slight margin of error
@@ -156,6 +161,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.client, called = Loc.server)
public static void dropItem(Player player, float angle){
if(player == null) return;
if(net.server() && player.unit().stack.amount <= 0){
throw new ValidateException(player, "Player cannot drop an item.");
}
@@ -179,6 +186,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Building tile){
if(player == null || tile == null) return;
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.depositItem, tile.tile(), action -> {
action.itemAmount = player.unit().stack.amount;
@@ -217,6 +225,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void unitControl(Player player, @Nullable Unit unit){
if(player == null) return;
//clear player unit when they possess a core
if((unit instanceof BlockUnitc && ((BlockUnitc)unit).tile() instanceof CoreBuild)){
Fx.spawn.at(player);
@@ -226,10 +236,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.clearUnit();
player.deathTimer = 61f;
((CoreBuild)((BlockUnitc)unit).tile()).requestSpawn(player);
}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() && !unit.dead){
player.unit(unit);
Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit));
if(!player.dead()){
@@ -257,23 +269,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(commander.isCommanding()){
commander.clearCommand();
}else{
FormationPattern pattern = new SquareFormation();
Formation formation = new Formation(new Vec3(player.x, player.y, player.unit().rotation), pattern);
formation.slotAssignmentStrategy = new DistanceAssignmentStrategy(pattern);
units.clear();
commander.commandNearby(new SquareFormation());
Fx.commandSend.at(player);
Units.nearby(player.team(), player.x, player.y, 200f, u -> {
if(u.isAI()){
units.add(u);
}
});
units.sort(u -> u.dst2(player.unit()));
units.truncate(player.unit().type().commandLimit);
commander.command(formation, units);
}
}
@@ -306,7 +304,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() && !u.dead);
if(unit != null){
Call.unitControl(player, unit);
@@ -316,7 +314,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() && !u.dead);
if(unit == null && controlledType == UnitTypes.block){
unit = world.buildWorld(player.x, player.y) instanceof ControlBlock ? ((ControlBlock)world.buildWorld(player.x, player.y)).unit() : null;
}
@@ -376,7 +374,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public void updateState(){
if(state.isMenu()){
controlledType = null;
}
}
public void drawBottom(){
@@ -542,11 +542,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(test.get(req)) return req;
}
for(BuildPlan req : selectRequests){
if(test.get(req)) return req;
}
return null;
return selectRequests.find(test);
}
protected void drawBreakSelection(int x1, int y1, int x2, int y2){
@@ -877,7 +873,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
Building tile = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
if(tile instanceof ControlBlock && tile.team() == player.team()){
if(tile instanceof ControlBlock && tile.team == player.team()){
return ((ControlBlock)tile).unit();
}

View File

@@ -31,39 +31,39 @@ public class MobileInput extends InputHandler implements GestureListener{
/** Maximum speed the player can pan. */
private static final float maxPanSpeed = 1.3f;
/** Distance to edge of screen to start panning. */
private final float edgePan = Scl.scl(60f);
public final float edgePan = Scl.scl(60f);
//gesture data
private Vec2 vector = new Vec2(), movement = new Vec2(), targetPos = new Vec2();
private float lastZoom = -1;
public Vec2 vector = new Vec2(), movement = new Vec2(), targetPos = new Vec2();
public float lastZoom = -1;
/** Position where the player started dragging a line. */
private int lineStartX, lineStartY, lastLineX, lastLineY;
public int lineStartX, lineStartY, lastLineX, lastLineY;
/** Animation scale for line. */
private float lineScale;
public float lineScale;
/** Animation data for crosshair. */
private float crosshairScale;
private Teamc lastTarget;
public float crosshairScale;
public Teamc lastTarget;
/** Used for shifting build requests. */
private float shiftDeltaX, shiftDeltaY;
public float shiftDeltaX, shiftDeltaY;
/** Place requests to be removed. */
private Seq<BuildPlan> removals = new Seq<>();
public Seq<BuildPlan> removals = new Seq<>();
/** Whether or not the player is currently shifting all placed tiles. */
private boolean selecting;
public boolean selecting;
/** Whether the player is currently in line-place mode. */
private boolean lineMode, schematicMode;
public boolean lineMode, schematicMode;
/** Current place mode. */
private PlaceMode mode = none;
public PlaceMode mode = none;
/** Whether no recipe was available when switching to break mode. */
private Block lastBlock;
public Block lastBlock;
/** Last placed request. Used for drawing block overlay. */
private BuildPlan lastPlaced;
public BuildPlan lastPlaced;
/** Down tracking for panning.*/
private boolean down = false;
public boolean down = false;
private Teamc target, moveTarget;
public Teamc target, moveTarget;
//region utility methods
@@ -77,7 +77,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}else{
Building tile = world.buildWorld(x, y);
if(tile != null && player.team().isEnemy(tile.team())){
if(tile != null && player.team().isEnemy(tile.team)){
player.miner().mineTile(null);
target = tile;
}else if(tile != null && player.unit().type().canHeal && tile.team == player.team() && tile.damaged()){
@@ -555,7 +555,9 @@ public class MobileInput extends InputHandler implements GestureListener{
if(cursor == null || Core.scene.hasMouse(x, y)) return false;
Tile linked = cursor.build == null ? cursor : cursor.build.tile();
checkTargets(worldx, worldy);
if(!player.dead()){
checkTargets(worldx, worldy);
}
//remove if request present
if(hasRequest(cursor)){
@@ -850,19 +852,9 @@ public class MobileInput extends InputHandler implements GestureListener{
movement.set(targetPos).sub(player).limit(speed);
movement.setAngle(Mathf.slerp(movement.angle(), unit.vel.angle(), 0.05f));
//pathfind for ground units
if(!flying && !type.canBoost && !(unit instanceof WaterMovec)){
Tile on = unit.tileOn();
if(on != null && !on.solid()){
Tile to = pathfinder.getTargetTile(unit.tileOn(), unit.team, targetPos);
if(to != null){
movement.set(to).sub(unit).setLength(speed);
}
}
}
if(player.within(targetPos, attractDst)){
movement.setZero();
unit.vel.approachDelta(Vec2.ZERO, type.speed * type.accel / 2f);
}
float expansion = 3f;

View File

@@ -68,7 +68,7 @@ public class JsonIO{
return json.prettyPrint(in);
}
private static void apply(Json json){
static void apply(Json json){
json.setIgnoreUnknownFields(true);
json.setElementType(Rules.class, "spawns", SpawnGroup.class);
json.setElementType(Rules.class, "loadout", ItemStack.class);

View File

@@ -34,7 +34,7 @@ public abstract class SaveFileReader{
"titan-factory", "legacy-unit-factory",
"fortress-factory", "legacy-unit-factory",
"command-center", "legacy-command-center"
"mass-conveyor", "payload-conveyor"
);
protected int lastRegionLength;

View File

@@ -92,6 +92,9 @@ public class TypeIO{
write.b((byte)14);
write.i(((byte[])object).length);
write.b((byte[])object);
}else if(object instanceof UnitCommand){
write.b((byte)15);
write.b(((UnitCommand)object).ordinal());
}else{
throw new IllegalArgumentException("Unknown object type: " + object.getClass());
}
@@ -116,6 +119,7 @@ public class TypeIO{
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;
case 15: return UnitCommand.all[read.b()];
default: throw new IllegalArgumentException("Unknown object type: " + type);
}
}

View File

@@ -74,10 +74,10 @@ public abstract class LegacySaveVersion extends SaveVersion{
tile.setTeam(Team.get(team));
tile.build.rotation = rotation;
if(tile.build.items != null) tile.build.items.read(Reads.get(stream));
if(tile.build.power != null) tile.build.power.read(Reads.get(stream));
if(tile.build.liquids != null) tile.build.liquids.read(Reads.get(stream));
if(tile.build.cons != null) tile.build.cons.read(Reads.get(stream));
if(tile.build.items != null) tile.build.items.read(Reads.get(stream), true);
if(tile.build.power != null) tile.build.power.read(Reads.get(stream), true);
if(tile.build.liquids != null) tile.build.liquids.read(Reads.get(stream), true);
if(tile.build.cons != null) tile.build.cons.read(Reads.get(stream), true);
//read only from subclasses!
tile.build.read(Reads.get(in), version);

View File

@@ -1,46 +0,0 @@
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

@@ -96,7 +96,7 @@ public class LAssembler{
if(c == '"'){
inString = !inString;
}else if(c == ' ' && !inString){
tokens.add(line.substring(lastIdx, i).replace("\\n", "\n"));
tokens.add(line.substring(lastIdx, i));
lastIdx = i + 1;
}
}
@@ -106,6 +106,32 @@ public class LAssembler{
arr = new String[]{line};
}
String type = arr[0];
//legacy stuff
if(type.equals("bop")){
arr[0] = "op";
//field order for bop used to be op a, b, result, but now it's op result a b
String res = arr[4];
arr[4] = arr[3];
arr[3] = arr[2];
arr[2] = res;
}else if(type.equals("uop")){
arr[0] = "op";
if(arr[1].equals("negate")){
arr = new String[]{
"op", "mul", arr[3], arr[2], "-1"
};
}else{
//field order for uop used to be op a, result, but now it's op result a
String res = arr[3];
arr[3] = arr[2];
arr[2] = res;
}
}
LStatement st = LogicIO.read(arr);
if(st != null){
@@ -121,6 +147,7 @@ public class LAssembler{
}
}
}catch(Exception parseFailed){
parseFailed.printStackTrace();
//when parsing fails, add a dummy invalid statement
statements.add(new InvalidStatement());
}
@@ -135,7 +162,7 @@ public class LAssembler{
//string case
if(symbol.startsWith("\"") && symbol.endsWith("\"")){
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1)).id;
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
}
try{

View File

@@ -20,9 +20,8 @@ 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();
static Seq<Runnable> postDraw = new Seq<>();
static Seq<Runnable> postDrawPriority = new Seq<>();
DragLayout statements;
StatementElem dragging;
@@ -91,11 +90,27 @@ public class LCanvas extends Table{
this.statements.layout();
}
@Override
public void act(float delta){
super.act(delta);
if(Core.input.isTouched()){
float y = Core.input.mouseY();
float dst = Math.min(y - this.y, Core.graphics.getHeight() - y);
if(dst < Scl.scl(100f)){ //scroll margin
int sign = Mathf.sign(Core.graphics.getHeight()/2f - y);
pane.setScrollY(pane.getScrollY() + sign * Scl.scl(15f));
}
}
}
@Override
public void draw(){
postDraw.clear();
postDrawPriority.clear();
super.draw();
postDraw.each(Runnable::run);
postDrawPriority.each(Runnable::run);
}
public class DragLayout extends WidgetGroup{
@@ -252,7 +267,7 @@ public class LCanvas extends Table{
return false;
}
Vec2 v = localToStageCoordinates(Tmp.v1.set(x, y));
Vec2 v = localToParentCoordinates(Tmp.v1.set(x, y));
lastx = v.x;
lasty = v.y;
dragging = StatementElem.this;
@@ -263,7 +278,7 @@ public class LCanvas extends Table{
@Override
public void touchDragged(InputEvent event, float x, float y, int pointer){
Vec2 v = localToStageCoordinates(Tmp.v1.set(x, y));
Vec2 v = localToParentCoordinates(Tmp.v1.set(x, y));
translation.add(v.x - lastx, v.y - lasty);
lastx = v.x;
@@ -317,16 +332,18 @@ public class LCanvas extends Table{
}
public static class JumpButton extends ImageButton{
Color hoverColor = Pal.place;
Color defaultColor = Color.white;
@NonNull Prov<StatementElem> to;
boolean selecting;
float mx, my;
ClickListener listener;
public JumpButton(Color color, @NonNull Prov<StatementElem> getter, Cons<StatementElem> setter){
public JumpButton(@NonNull Prov<StatementElem> getter, Cons<StatementElem> setter){
super(Tex.logicNode, Styles.colori);
to = getter;
getStyle().imageUpColor = color;
addListener(listener = new ClickListener());
addListener(new InputListener(){
@Override
@@ -362,6 +379,9 @@ public class LCanvas extends Table{
if(to.get() != null && to.get().parent == null){
setter.get(null);
}
setColor(listener.isOver() ? hoverColor : defaultColor);
getStyle().imageUpColor = this.color;
});
}
@@ -369,7 +389,7 @@ public class LCanvas extends Table{
public void draw(){
super.draw();
postDraw.add(() -> {
(listener.isOver() ? postDrawPriority : postDraw).add(() -> {
Element hover = to.get() == null && selecting ? hovered() : to.get();
float tx = 0, ty = 0;
boolean draw = false;
@@ -402,10 +422,12 @@ public class LCanvas extends Table{
}
if(draw){
drawCurve(rx + width/2f, ry + height/2f, tx, ty, color);
drawCurve(rx + width/2f, ry + height/2f, tx, ty);
float s = width;
Draw.color(color);
Tex.logicNode.draw(tx + s*0.75f, ty - s/2f, -s, s);
Draw.reset();
}
});
}
@@ -421,7 +443,7 @@ public class LCanvas extends Table{
return (StatementElem)e;
}
void drawCurve(float x, float y, float x2, float y2, Color color){
void drawCurve(float x, float y, float x2, float y2){
Lines.stroke(4f, color);
Draw.alpha(parentAlpha);
@@ -434,8 +456,6 @@ public class LCanvas extends Table{
x2, y2,
Math.max(20, (int)(Mathf.dst(x, y, x2, y2) / 5))
);
Draw.reset();
}
}
}

View File

@@ -7,7 +7,7 @@ public enum LCategory{
blocks(Pal.accentBack),
control(Color.cyan.cpy().shiftSaturation(-0.6f).mul(0.7f)),
operations(Pal.place.cpy().shiftSaturation(-0.5f).mul(0.7f)),
io(Pal.remove.cpy().shiftSaturation(-0.5f).mul(0.7f));;
io(Pal.remove.cpy().shiftSaturation(-0.5f).mul(0.7f));
public final Color color;

View File

@@ -21,7 +21,8 @@ public class LExecutor{
varTime = 1;
public static final int
maxGraphicsBuffer = 512,
maxGraphicsBuffer = 256,
maxDisplayBuffer = 512,
maxTextBuffer = 256;
public LInstruction[] instructions = {};
@@ -177,9 +178,7 @@ public class LExecutor{
public void run(LExecutor exec){
int address = exec.numi(index);
if(address >= 0 && address < exec.links.length){
exec.setobj(output, exec.links[address]);
}
exec.setobj(output, address >= 0 && address < exec.links.length ? exec.links[address] : null);
}
}
@@ -379,40 +378,26 @@ public class LExecutor{
}
}
public static class BinaryOpI implements LInstruction{
public BinaryOp op = BinaryOp.add;
public static class OpI implements LInstruction{
public LogicOp op = LogicOp.add;
public int a, b, dest;
public BinaryOpI(BinaryOp op, int a, int b, int dest){
public OpI(LogicOp op, int a, int b, int dest){
this.op = op;
this.a = a;
this.b = b;
this.dest = dest;
}
BinaryOpI(){}
OpI(){}
@Override
public void run(LExecutor exec){
exec.setnum(dest, op.function.get(exec.num(a), exec.num(b)));
}
}
public static class UnaryOpI implements LInstruction{
public UnaryOp op = UnaryOp.negate;
public int value, dest;
public UnaryOpI(UnaryOp op, int value, int dest){
this.op = op;
this.value = value;
this.dest = dest;
}
UnaryOpI(){}
@Override
public void run(LExecutor exec){
exec.setnum(dest, op.function.get(exec.num(value)));
if(op.unary){
exec.setnum(dest, op.function1.get(exec.num(a)));
}else{
exec.setnum(dest, op.function2.get(exec.num(a), exec.num(b)));
}
}
}
@@ -478,8 +463,10 @@ public class LExecutor{
Building build = exec.building(target);
if(build instanceof LogicDisplayBuild){
LogicDisplayBuild d = (LogicDisplayBuild)build;
for(int i = 0; i < exec.graphicsBuffer.size; i++){
d.commands.addLast(exec.graphicsBuffer.items[i]);
if(d.commands.size + exec.graphicsBuffer.size < maxDisplayBuffer){
for(int i = 0; i < exec.graphicsBuffer.size; i++){
d.commands.addLast(exec.graphicsBuffer.items[i]);
}
}
exec.graphicsBuffer.clear();
}

View File

@@ -1,7 +1,6 @@
package mindustry.logic;
import arc.func.*;
import arc.graphics.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
@@ -56,15 +55,23 @@ public class LStatements{
}
}
@RegisterStatement("getlink")
public static class GetLinkStatement extends LStatement{
public String output = "result", address = "0";
@RegisterStatement("read")
public static class ReadStatement extends LStatement{
public String output = "result", target = "cell1", address = "0";
@Override
public void build(Table table){
table.add(" read ");
field(table, output, str -> output = str);
table.add(" = link# ");
table.add(" = ");
fields(table, target, str -> target = str);
row(table);
table.add(" at ");
field(table, address, str -> address = str);
}
@@ -76,7 +83,7 @@ public class LStatements{
@Override
public LInstruction build(LAssembler builder){
return new GetLinkI(builder.var(output), builder.var(address));
return new ReadI(builder.var(target), builder.var(address), builder.var(output));
}
}
@@ -112,38 +119,6 @@ public class LStatements{
}
}
@RegisterStatement("read")
public static class ReadStatement extends LStatement{
public String output = "result", target = "cell1", address = "0";
@Override
public void build(Table table){
table.add(" read ");
field(table, output, str -> output = str);
table.add(" = ");
fields(table, target, str -> target = str);
row(table);
table.add(" at ");
field(table, address, str -> address = str);
}
@Override
public LCategory category(){
return LCategory.io;
}
@Override
public LInstruction build(LAssembler builder){
return new ReadI(builder.var(target), builder.var(address), builder.var(output));
}
}
@RegisterStatement("draw")
public static class DrawStatement extends LStatement{
public GraphicsType type = GraphicsType.clear;
@@ -253,6 +228,26 @@ public class LStatements{
}
}
@RegisterStatement("print")
public static class PrintStatement extends LStatement{
public String value = "\"frog\"";
@Override
public void build(Table table){
field(table, value, str -> value = str).width(0f).growX().padRight(3);
}
@Override
public LInstruction build(LAssembler builder){
return new PrintI(builder.var(value));
}
@Override
public LCategory category(){
return LCategory.io;
}
}
@RegisterStatement("drawflush")
public static class DrawFlushStatement extends LStatement{
public String target = "display1";
@@ -274,26 +269,6 @@ public class LStatements{
}
}
@RegisterStatement("print")
public static class PrintStatement extends LStatement{
public String value = "\"frog\"";
@Override
public void build(Table table){
field(table, value, str -> value = str).width(0f).growX().padRight(3);
}
@Override
public LInstruction build(LAssembler builder){
return new PrintI(builder.var(value));
}
@Override
public LCategory category(){
return LCategory.control;
}
}
@RegisterStatement("printflush")
public static class PrintFlushStatement extends LStatement{
public String target = "message1";
@@ -315,6 +290,30 @@ public class LStatements{
}
}
@RegisterStatement("getlink")
public static class GetLinkStatement extends LStatement{
public String output = "result", address = "0";
@Override
public void build(Table table){
field(table, output, str -> output = str);
table.add(" = link# ");
field(table, address, str -> address = str);
}
@Override
public LCategory category(){
return LCategory.blocks;
}
@Override
public LInstruction build(LAssembler builder){
return new GetLinkI(builder.var(output), builder.var(address));
}
}
@RegisterStatement("control")
public static class ControlStatement extends LStatement{
public LAccess type = LAccess.enabled;
@@ -372,7 +371,7 @@ public class LStatements{
public static class RadarStatement extends LStatement{
public RadarTarget target1 = RadarTarget.enemy, target2 = RadarTarget.any, target3 = RadarTarget.any;
public RadarSort sort = RadarSort.distance;
public String radar = "turret1", sortOrder = "0", output = "result";
public String radar = "turret1", sortOrder = "1", output = "result";
@Override
public void build(Table table){
@@ -561,62 +560,51 @@ public class LStatements{
}
}
@RegisterStatement("bop")
public static class BinaryOpStatement extends LStatement{
public BinaryOp op = BinaryOp.add;
public String a = "a", b = "b", dest = "result";
@RegisterStatement("op")
public static class OperationStatement extends LStatement{
public LogicOp op = LogicOp.add;
public String dest = "result", a = "a", b = "b";
@Override
public void build(Table table){
rebuild(table);
}
void rebuild(Table table){
table.clearChildren();
field(table, dest, str -> dest = str);
table.add(" = ");
row(table);
if(op.unary){
opButton(table);
field(table, a, str -> a = str);
field(table, a, str -> a = str);
}else{
row(table);
field(table, a, str -> a = str);
opButton(table);
field(table, b, str -> b = str);
}
}
void opButton(Table table){
table.button(b -> {
b.label(() -> op.symbol);
b.clicked(() -> showSelect(b, BinaryOp.all, op, o -> op = o));
b.clicked(() -> showSelect(b, LogicOp.all, op, o -> {
op = o;
rebuild(table);
}));
}, Styles.logict, () -> {}).size(60f, 40f).pad(4f).color(table.color);
field(table, b, str -> b = str);
}
@Override
public LInstruction build(LAssembler builder){
return new BinaryOpI(op,builder.var(a), builder.var(b), builder.var(dest));
}
@Override
public LCategory category(){
return LCategory.operations;
}
}
@RegisterStatement("uop")
public static class UnaryOpStatement extends LStatement{
public UnaryOp op = UnaryOp.negate;
public String value = "b", dest = "result";
@Override
public void build(Table table){
field(table, dest, str -> dest = str);
table.add(" = ");
table.button(b -> {
b.label(() -> op.symbol);
b.clicked(() -> showSelect(b, UnaryOp.all, op, o -> op = o));
}, Styles.logict, () -> {}).size(50f, 40f).pad(3f).color(table.color);
field(table, value, str -> value = str);
}
@Override
public LInstruction build(LAssembler builder){
return new UnaryOpI(op, builder.var(value), builder.var(dest));
return new OpI(op,builder.var(a), builder.var(b), builder.var(dest));
}
@Override
@@ -666,7 +654,7 @@ public class LStatements{
field(table, compare, str -> compare = str);
table.add().growX();
table.add(new JumpButton(Color.white, () -> dest, s -> dest = s)).size(30).right().padLeft(-8);
table.add(new JumpButton(() -> dest, s -> dest = s)).size(30).right().padLeft(-8);
}
//elements need separate conversion logic

View File

@@ -36,7 +36,7 @@ public class LogicDialog extends BaseDialog{
t.button("@schematic.copy.import", Icon.download, style, () -> {
dialog.hide();
try{
canvas.load(Core.app.getClipboardText());
canvas.load(Core.app.getClipboardText().replace("\r\n", "\n"));
}catch(Throwable e){
ui.showException(e);
}
@@ -61,7 +61,12 @@ public class LogicDialog extends BaseDialog{
for(Prov<LStatement> prov : LogicIO.allStatements){
LStatement example = prov.get();
if(example instanceof InvalidStatement) continue;
t.button(example.name(), Styles.cleart, () -> {
TextButtonStyle style = new TextButtonStyle(Styles.cleart);
style.fontColor = example.category().color;
style.font = Fonts.outline;
t.button(example.name(), style, () -> {
canvas.add(prov.get());
dialog.hide();
}).size(140f, 50f);

View File

@@ -0,0 +1,75 @@
package mindustry.logic;
import arc.math.*;
public enum LogicOp{
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)),
not("not", a -> ~(int)(a)),
abs("abs", a -> Math.abs(a)),
log("log", Math::log),
log10("log10", Math::log10),
sin("sin", d -> Math.sin(d * 0.017453292519943295D)),
cos("cos", d -> Math.cos(d * 0.017453292519943295D)),
tan("tan", d -> Math.tan(d * 0.017453292519943295D)),
floor("floor", Math::floor),
ceil("ceil", Math::ceil),
sqrt("sqrt", Math::sqrt),
rand("rand", d -> Mathf.rand.nextDouble() * d),
;
public static final LogicOp[] all = values();
public final OpLambda2 function2;
public final OpLambda1 function1;
public final boolean unary;
public final String symbol;
LogicOp(String symbol, OpLambda2 function){
this.symbol = symbol;
this.function2 = function;
this.function1 = null;
this.unary = false;
}
LogicOp(String symbol, OpLambda1 function){
this.symbol = symbol;
this.function1 = function;
this.function2 = null;
this.unary = true;
}
@Override
public String toString(){
return symbol;
}
interface OpLambda2{
double get(double a, double b);
}
interface OpLambda1{
double get(double a);
}
}

View File

@@ -1,38 +0,0 @@
package mindustry.logic;
import arc.math.*;
public enum UnaryOp{
negate("-", a -> -a),
not("not", a -> ~(int)(a)),
abs("abs", Math::abs),
log("log", Math::log),
log10("log10", Math::log10),
sin("sin", d -> Math.sin(d * 0.017453292519943295D)),
cos("cos", d -> Math.cos(d * 0.017453292519943295D)),
tan("tan", d -> Math.tan(d * 0.017453292519943295D)),
floor("floor", Math::floor),
ceil("ceil", Math::ceil),
sqrt("sqrt", Math::sqrt),
rand("rand", d -> Mathf.rand.nextDouble() * d),
;
public static final UnaryOp[] all = values();
public final UnaryOpLambda function;
public final String symbol;
UnaryOp(String symbol, UnaryOpLambda function){
this.symbol = symbol;
this.function = function;
}
@Override
public String toString(){
return symbol;
}
interface UnaryOpLambda{
double get(double a);
}
}

View File

@@ -32,7 +32,7 @@ public class Maps{
/** List of all built-in maps. Filenames only. */
private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "veins", "glacier"};
/** Maps tagged as PvP */
private static final String[] pvpMaps = {"veins", "glacier"};
static final String[] pvpMaps = {"veins", "glacier"};
/** All maps stored in an ordered array. */
private Seq<Map> maps = new Seq<>();
/** Serializer for meta. */

View File

@@ -35,9 +35,9 @@ import java.lang.reflect.*;
@SuppressWarnings("unchecked")
public class ContentParser{
private static final boolean ignoreUnknownFields = true;
private ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
private ObjectMap<Class<?>, FieldParser> classParsers = new ObjectMap<Class<?>, FieldParser>(){{
ObjectMap<Class<?>, FieldParser> classParsers = new ObjectMap<Class<?>, FieldParser>(){{
put(Effect.class, (type, data) -> field(Fx.class, data));
put(Schematic.class, (type, data) -> {
Object result = fieldOpt(Loadouts.class, data);
@@ -105,7 +105,7 @@ public class ContentParser{
private Seq<Runnable> reads = new Seq<>();
private Seq<Runnable> postreads = new Seq<>();
private ObjectSet<Object> toBeParsed = new ObjectSet<>();
private LoadedMod currentMod;
LoadedMod currentMod;
private Content currentContent;
private Json parser = new Json(){
@@ -480,7 +480,7 @@ public class ContentParser{
return first != null ? first : Vars.content.getByName(type, currentMod.name + "-" + name);
}
private <T> T make(Class<T> type){
<T> T make(Class<T> type){
try{
Constructor<T> cons = type.getDeclaredConstructor();
cons.setAccessible(true);
@@ -515,7 +515,7 @@ public class ContentParser{
}
}
private Object field(Class<?> type, JsonValue value){
Object field(Class<?> type, JsonValue value){
return field(type, value.asString());
}
@@ -530,7 +530,7 @@ public class ContentParser{
}
}
private Object fieldOpt(Class<?> type, JsonValue value){
Object fieldOpt(Class<?> type, JsonValue value){
try{
return type.getField(value.asString()).get(null);
}catch(Exception e){
@@ -538,7 +538,7 @@ public class ContentParser{
}
}
private void checkNullFields(Object object){
void checkNullFields(Object object){
if(object instanceof Number || object instanceof String || toBeParsed.contains(object)) return;
parser.getFields(object.getClass()).values().toSeq().each(field -> {
@@ -559,7 +559,7 @@ public class ContentParser{
readFields(object, jsonMap);
}
private void readFields(Object object, JsonValue jsonMap){
void readFields(Object object, JsonValue jsonMap){
toBeParsed.remove(object);
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = parser.getFields(type);
@@ -593,7 +593,7 @@ public class ContentParser{
}
/** Tries to resolve a class from a list of potential class names. */
private <T> Class<T> resolve(String base, String... potentials){
<T> Class<T> resolve(String base, String... potentials){
if(!base.isEmpty() && Character.isLowerCase(base.charAt(0))) base = Strings.capitalize(base);
for(String type : potentials){

View File

@@ -0,0 +1,79 @@
package mindustry.mod;
import arc.audio.*;
import arc.mock.*;
import arc.util.ArcAnnotate.*;
public class ModLoadingMusic implements Music{
public @NonNull Music music = new MockMusic();
@Override
public void play(){
music.play();
}
@Override
public void pause(){
music.pause();
}
@Override
public void stop(){
music.stop();
}
@Override
public boolean isPlaying(){
return music.isPlaying();
}
@Override
public boolean isLooping(){
return music.isLooping();
}
@Override
public void setLooping(boolean isLooping){
music.setLooping(isLooping);
}
@Override
public float getVolume(){
return music.getVolume();
}
@Override
public void setVolume(float volume){
music.setVolume(volume);
}
@Override
public void setPan(float pan, float volume){
music.setPan(pan, volume);
}
@Override
public float getPosition(){
return music.getPosition();
}
@Override
public void setPosition(float position){
music.setPosition(position);
}
@Override
public void dispose(){
music.dispose();
}
@Override
public void setCompletionListener(OnCompletionListener listener){
music.setCompletionListener(listener);
}
@Override
public boolean isDisposed(){
return music.isDisposed();
}
}

View File

@@ -39,7 +39,7 @@ public class Mods implements Loadable{
private int totalSprites;
private MultiPacker packer;
private Seq<LoadedMod> mods = new Seq<>();
Seq<LoadedMod> mods = new Seq<>();
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
private boolean requiresReload, createdAtlas;
@@ -454,7 +454,7 @@ public class Mods implements Loadable{
content.setCurrentMod(null);
}
Log.debug("Time to initialize modded scripts: @", Time.elapsed());
Log.info("Time to initialize modded scripts: @", Time.elapsed());
}
/** Creates all the content found in mod files. */
@@ -586,58 +586,70 @@ public class Mods implements Loadable{
/** Loads a mod file+meta, but does not add it to the list.
* Note that directories can be loaded as mods.*/
private LoadedMod loadMod(Fi sourceFile) throws Exception{
Fi zip = sourceFile.isDirectory() ? sourceFile : new ZipFi(sourceFile);
if(zip.list().length == 1 && zip.list()[0].isDirectory()){
zip = zip.list()[0];
}
Time.mark();
Fi metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("mod.hjson").exists() ? zip.child("mod.hjson") : zip.child("plugin.json");
if(!metaf.exists()){
Log.warn("Mod @ doesn't have a 'mod.json'/'mod.hjson'/'plugin.json' file, skipping.", sourceFile);
throw new IllegalArgumentException("Invalid file: No mod.json found.");
}
ZipFi rootZip = null;
ModMeta meta = json.fromJson(ModMeta.class, Jval.read(metaf.readString()).toString(Jformat.plain));
meta.cleanup();
String camelized = meta.name.replace(" ", "");
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
String baseName = meta.name.toLowerCase().replace(" ", "-");
if(mods.contains(m -> m.name.equals(baseName))){
throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported.");
}
Mod mainMod;
Fi mainFile = zip;
String[] path = (mainClass.replace('.', '/') + ".class").split("/");
for(String str : path){
if(!str.isEmpty()){
mainFile = mainFile.child(str);
}
}
//make sure the main class exists before loading it; if it doesn't just don't put it there
if(mainFile.exists()){
//mobile versions don't support class mods
if(mobile){
throw new IllegalArgumentException("Java class mods are not supported on mobile.");
try{
Fi zip = sourceFile.isDirectory() ? sourceFile : (rootZip = new ZipFi(sourceFile));
if(zip.list().length == 1 && zip.list()[0].isDirectory()){
zip = zip.list()[0];
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{sourceFile.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(mainClass);
metas.put(main, meta);
mainMod = (Mod)main.getDeclaredConstructor().newInstance();
}else{
mainMod = null;
}
Fi metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("mod.hjson").exists() ? zip.child("mod.hjson") : zip.child("plugin.json");
if(!metaf.exists()){
Log.warn("Mod @ doesn't have a 'mod.json'/'mod.hjson'/'plugin.json' file, skipping.", sourceFile);
throw new IllegalArgumentException("Invalid file: No mod.json found.");
}
//all plugins are hidden implicitly
if(mainMod instanceof Plugin){
meta.hidden = true;
}
ModMeta meta = json.fromJson(ModMeta.class, Jval.read(metaf.readString()).toString(Jformat.plain));
meta.cleanup();
String camelized = meta.name.replace(" ", "");
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
String baseName = meta.name.toLowerCase().replace(" ", "-");
return new LoadedMod(sourceFile, zip, mainMod, meta);
if(mods.contains(m -> m.name.equals(baseName))){
throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported.");
}
Mod mainMod;
Fi mainFile = zip;
String[] path = (mainClass.replace('.', '/') + ".class").split("/");
for(String str : path){
if(!str.isEmpty()){
mainFile = mainFile.child(str);
}
}
//make sure the main class exists before loading it; if it doesn't just don't put it there
if(mainFile.exists()){
//mobile versions don't support class mods
if(mobile){
throw new IllegalArgumentException("Java class mods are not supported on mobile.");
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{sourceFile.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(mainClass);
metas.put(main, meta);
mainMod = (Mod)main.getDeclaredConstructor().newInstance();
}else{
mainMod = null;
}
//all plugins are hidden implicitly
if(mainMod instanceof Plugin){
meta.hidden = true;
}
Log.info("Loaded mod '@' in @", meta.name, Time.elapsed());
return new LoadedMod(sourceFile, zip, mainMod, meta);
}catch(Exception e){
//delete root zip file so it can be closed on windows
if(rootZip != null) rootZip.delete();
throw e;
}
}
/** Represents a mod's state. May be a jar file, folder or zip. */

View File

@@ -1,7 +1,10 @@
package mindustry.mod;
import arc.*;
import arc.assets.*;
import arc.audio.*;
import arc.files.*;
import arc.mock.*;
import arc.struct.*;
import arc.util.*;
import arc.util.Log.*;
@@ -23,7 +26,7 @@ public class Scripts implements Disposable{
private final Context context;
private final Scriptable scope;
private boolean errored;
private LoadedMod currentMod = null;
LoadedMod currentMod = null;
public Scripts(){
Time.mark();
@@ -73,7 +76,7 @@ public class Scripts implements Disposable{
Log.log(level, "[@]: @", source, message);
}
//utility mod functions
//region utility mod functions
public String readString(String path){
return Vars.tree.get(path, true).readString();
@@ -83,6 +86,38 @@ public class Scripts implements Disposable{
return Vars.tree.get(path, true).readBytes();
}
public Sound loadSound(String soundName){
if(Vars.headless) return new MockSound();
String name = "sounds/" + soundName;
String path = Vars.tree.get(name + ".ogg").exists() && !Vars.ios ? name + ".ogg" : name + ".mp3";
if(Core.assets.contains(path, Sound.class)) return Core.assets.get(path, Sound.class);
ModLoadingSound sound = new ModLoadingSound();
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class);
desc.loaded = result -> sound.sound = (Sound)result;
desc.errored = Throwable::printStackTrace;
return sound;
}
public Music loadMusic(String soundName){
if(Vars.headless) return new MockMusic();
String name = "music/" + soundName;
String path = Vars.tree.get(name + ".ogg").exists() && !Vars.ios ? name + ".ogg" : name + ".mp3";
if(Core.assets.contains(path, Music.class)) return Core.assets.get(path, Music.class);
ModLoadingMusic sound = new ModLoadingMusic();
AssetDescriptor<?> desc = Core.assets.load(path, Music.class);
desc.loaded = result -> sound.music = (Music)result;
desc.errored = Throwable::printStackTrace;
return sound;
}
//endregion
public void run(LoadedMod mod, Fi file){
currentMod = mod;
run(file.readString(), file.name(), true);

View File

@@ -5,6 +5,7 @@ import arc.func.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.Log.*;
import arc.util.pooling.Pool.*;
import arc.util.pooling.*;
import mindustry.*;
@@ -18,13 +19,14 @@ import static mindustry.Vars.*;
import static mindustry.game.EventType.*;
public class Administration{
/** All player info. Maps UUIDs to info. This persists throughout restarts. */
public Seq<String> bannedIPs = new Seq<>();
public Seq<String> whitelist = new Seq<>();
public Seq<ChatFilter> chatFilters = new Seq<>();
public Seq<ActionFilter> actionFilters = new Seq<>();
public Seq<String> subnetBans = new Seq<>();
/** All player info. Maps UUIDs to info. This persists throughout restarts. Do not access directly. */
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
private Seq<String> bannedIPs = new Seq<>();
private Seq<String> whitelist = new Seq<>();
private Seq<ChatFilter> chatFilters = new Seq<>();
private Seq<ActionFilter> actionFilters = new Seq<>();
private Seq<String> subnetBans = new Seq<>();
private IntIntMap lastPlaced = new IntIntMap();
public Administration(){
@@ -72,12 +74,17 @@ public class Administration{
});
//block interaction rate limit
//TODO when someone disconnects, a different player is mistakenly kicked for spamming actions
addActionFilter(action -> {
if(action.type != ActionType.breakBlock &&
action.type != ActionType.placeBlock &&
Config.antiSpam.bool() &&
//make sure players can configure their own stuff, e.g. in schematics
lastPlaced.get(action.tile.pos(), -1) != action.player.id()){
Config.antiSpam.bool()){
//make sure players can configure their own stuff, e.g. in schematics - but only once.
if(lastPlaced.get(action.tile.pos(), -1) == action.player.id()){
lastPlaced.remove(action.tile.pos());
return true;
}
Ratekeeper rate = action.player.getInfo().rate;
if(rate.allow(Config.interactRateWindow.num() * 1000, Config.interactRateLimit.num())){
@@ -572,7 +579,8 @@ public class Administration{
motd("The message displayed to people on connection.", "off"),
autosave("Whether the periodically save the map when playing.", false),
autosaveAmount("The maximum amount of autosaves. Older ones get replaced.", 10),
autosaveSpacing("Spacing between autosaves in seconds.", 60 * 5);
autosaveSpacing("Spacing between autosaves in seconds.", 60 * 5),
debug("Enable debug logging", false, () -> Log.setLogLevel(debug() ? LogLevel.debug : LogLevel.info));
public static final Config[] all = values();
@@ -631,6 +639,10 @@ public class Administration{
Core.settings.put(key, value);
changed.run();
}
private static boolean debug(){
return Config.debug.bool();
}
}
public static class PlayerInfo{

View File

@@ -1,10 +1,10 @@
package mindustry.net;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.net.*;
import arc.net.FrameworkMessage.*;
import arc.struct.*;
import arc.util.*;
import arc.util.async.*;
import arc.util.pooling.*;
@@ -28,7 +28,9 @@ public class ArcNetProvider implements NetProvider{
Thread serverThread;
public ArcNetProvider(){
client = new Client(8192, 4096, new PacketSerializer());
ArcNet.errorHandler = e -> Log.debug(Strings.getStackTrace(e));
client = new Client(8192, 8192, new PacketSerializer());
client.setDiscoveryPacket(packetSupplier);
client.addListener(new NetListener(){
@Override
@@ -66,7 +68,7 @@ public class ArcNetProvider implements NetProvider{
}
});
server = new Server(4096 * 2, 4096, new PacketSerializer());
server = new Server(8192, 8192, new PacketSerializer());
server.setMulticast(multicastGroup, multicastPort);
server.setDiscoveryHandler((address, handler) -> {
ByteBuffer buffer = NetworkIO.writeServerData();
@@ -180,6 +182,7 @@ public class ArcNetProvider implements NetProvider{
Threads.daemon(() -> {
try{
DatagramSocket socket = new DatagramSocket();
long time = Time.millis();
socket.send(new DatagramPacket(new byte[]{-2, 1}, 2, InetAddress.getByName(address), port));
socket.setSoTimeout(2000);
@@ -187,7 +190,7 @@ public class ArcNetProvider implements NetProvider{
socket.receive(packet);
ByteBuffer buffer = ByteBuffer.wrap(packet.getData());
Host host = NetworkIO.readServerData(packet.getAddress().getHostAddress(), buffer);
Host host = NetworkIO.readServerData((int)Time.timeSinceMillis(time), packet.getAddress().getHostAddress(), buffer);
Core.app.post(() -> valid.get(host));
}catch(Exception e){
@@ -199,6 +202,7 @@ public class ArcNetProvider implements NetProvider{
@Override
public void discoverServers(Cons<Host> callback, Runnable done){
Seq<InetAddress> foundAddresses = new Seq<>();
long time = Time.millis();
client.discoverHosts(port, multicastGroup, multicastPort, 3000, packet -> {
Core.app.post(() -> {
try{
@@ -206,7 +210,7 @@ public class ArcNetProvider implements NetProvider{
return;
}
ByteBuffer buffer = ByteBuffer.wrap(packet.getData());
Host host = NetworkIO.readServerData(packet.getAddress().getHostAddress(), buffer);
Host host = NetworkIO.readServerData((int)Time.timeSinceMillis(time), packet.getAddress().getHostAddress(), buffer);
callback.get(host);
foundAddresses.add(packet.getAddress());
}catch(Exception e){
@@ -265,7 +269,7 @@ public class ArcNetProvider implements NetProvider{
return null;
}
private void handleException(Throwable e){
void handleException(Throwable e){
if(e instanceof ArcNetException){
Core.app.post(() -> net.showError(new IOException("mismatch")));
}else if(e instanceof ClosedChannelException){

View File

@@ -89,31 +89,38 @@ public class BeControl{
if(!headless){
checkUpdates = false;
ui.showCustomConfirm(Core.bundle.format("be.update", "") + " " + updateBuild, "@be.update.confirm", "@ok", "@be.ignore", () -> {
boolean[] cancel = {false};
float[] progress = {0};
int[] length = {0};
Fi file = bebuildDirectory.child("client-be-" + updateBuild + ".jar");
try{
boolean[] cancel = {false};
float[] progress = {0};
int[] length = {0};
Fi file = Fi.get(BeControl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
BaseDialog dialog = new BaseDialog("@be.updating");
download(updateUrl, file, i -> length[0] = i, v -> progress[0] = v, () -> cancel[0], () -> {
try{
Runtime.getRuntime().exec(new String[]{"java", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()});
System.exit(0);
}catch(IOException e){
BaseDialog dialog = new BaseDialog("@be.updating");
download(updateUrl, file, i -> length[0] = i, v -> progress[0] = v, () -> cancel[0], () -> {
try{
Runtime.getRuntime().exec(OS.isMac ?
new String[]{"java", "-XstartOnFirstThread", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()} :
new String[]{"java", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()}
);
System.exit(0);
}catch(IOException e){
ui.showException(e);
}
}, e -> {
dialog.hide();
ui.showException(e);
}
}, e -> {
dialog.hide();
ui.showException(e);
});
});
dialog.cont.add(new Bar(() -> length[0] == 0 ? Core.bundle.get("be.updating") : (int)(progress[0] * length[0]) / 1024/ 1024 + "/" + length[0]/1024/1024 + " MB", () -> Pal.accent, () -> progress[0])).width(400f).height(70f);
dialog.buttons.button("@cancel", Icon.cancel, () -> {
cancel[0] = true;
dialog.hide();
}).size(210f, 64f);
dialog.setFillParent(false);
dialog.show();
dialog.cont.add(new Bar(() -> length[0] == 0 ? Core.bundle.get("be.updating") : (int)(progress[0] * length[0]) / 1024/ 1024 + "/" + length[0]/1024/1024 + " MB", () -> Pal.accent, () -> progress[0])).width(400f).height(70f);
dialog.buttons.button("@cancel", Icon.cancel, () -> {
cancel[0] = true;
dialog.hide();
}).size(210f, 64f);
dialog.setFillParent(false);
dialog.show();
}catch(Exception e){
ui.showException(e);
}
}, () -> checkUpdates = false);
}else{
Log.info("&lcA new update is available: &lyBleeding Edge build @", updateBuild);
@@ -132,6 +139,7 @@ public class BeControl{
() -> Core.app.post(() -> {
Log.info("&lcSaving...");
SaveIO.save(saveDirectory.child("autosavebe." + saveExtension));
Log.info("&lcAutosaved.");
netServer.kickAll(KickReason.serverRestarting);
Threads.sleep(32);

View File

@@ -16,7 +16,8 @@ public class Host{
public final @Nullable String modeName;
public int ping, port = Vars.port;
public Host(String name, String address, String mapname, int wave, int players, int version, String versionType, Gamemode mode, int playerLimit, String description, String modeName){
public Host(int ping, String name, String address, String mapname, int wave, int players, int version, String versionType, Gamemode mode, int playerLimit, String description, String modeName){
this.ping = ping;
this.name = name;
this.address = address;
this.players = players;

View File

@@ -21,6 +21,7 @@ public abstract class NetConnection{
public @Nullable Player player;
public @Nullable Unitc lastUnit;
public Vec2 lastPosition = new Vec2();
public boolean kicked = false;
/** ID of last received client snapshot. */
public int lastReceivedClientSnapshot = -1;
@@ -38,6 +39,8 @@ public abstract class NetConnection{
/** Kick with a special, localized reason. Use this if possible. */
public void kick(KickReason reason){
if(kicked) return;
Log.info("Kicking connection @; Reason: @", address, reason.name());
if((reason == KickReason.kick || reason == KickReason.banned || reason == KickReason.vote)){
@@ -51,6 +54,7 @@ public abstract class NetConnection{
Time.runTask(2f, this::close);
netServer.admins.save();
kicked = true;
}
/** Kick with an arbitrary reason. */
@@ -60,6 +64,8 @@ public abstract class NetConnection{
/** Kick with an arbitrary reason, and a kick duration in milliseconds. */
public void kick(String reason, int kickDuration){
if(kicked) return;
Log.info("Kicking connection @; Reason: @", address, reason.replace("\n", " "));
PlayerInfo info = netServer.admins.getInfo(uuid);
@@ -71,6 +77,7 @@ public abstract class NetConnection{
Time.runTask(2f, this::close);
netServer.admins.save();
kicked = true;
}
public boolean isConnected(){

View File

@@ -88,7 +88,7 @@ public class NetworkIO{
return buffer;
}
public static Host readServerData(String hostAddress, ByteBuffer buffer){
public static Host readServerData(int ping, String hostAddress, ByteBuffer buffer){
String host = readString(buffer);
String map = readString(buffer);
int players = buffer.getInt();
@@ -100,7 +100,7 @@ public class NetworkIO{
String description = readString(buffer);
String modeName = readString(buffer);
return new Host(host, hostAddress, map, wave, players, version, vertype, gamemode, limit, description, modeName.isEmpty() ? null : modeName);
return new Host(ping, host, hostAddress, map, wave, players, version, vertype, gamemode, limit, description, modeName.isEmpty() ? null : modeName);
}
private static void writeString(ByteBuffer buffer, String string, int maxlen){

View File

@@ -42,7 +42,7 @@ public class UnitType extends UnlockableContent{
public float speed = 1.1f, boostMultiplier = 1f, rotateSpeed = 5f, baseRotateSpeed = 5f;
public float drag = 0.3f, accel = 0.5f, landShake = 0f, rippleScale = 1f, fallSpeed = 0.018f;
public float health = 200f, range = -1, armor = 0f;
public float crashDamageMultiplier = 4f;
public float crashDamageMultiplier = 3f;
public boolean targetAir = true, targetGround = true;
public boolean faceTarget = true, rotateShooting = true, isCounted = true, lowAltitude = false;
public boolean canBoost = false;
@@ -127,7 +127,7 @@ public class UnitType extends UnlockableContent{
public void display(Unit unit, Table table){
table.table(t -> {
t.left();
t.add(new Image(icon(Cicon.medium))).size(8 * 4);
t.add(new Image(icon(Cicon.medium))).size(8 * 4).scaling(Scaling.fit);
t.labelWrap(localizedName).left().width(190f).padLeft(5);
}).growX().left();
table.row();

View File

@@ -3,6 +3,7 @@ package mindustry.type;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
@@ -15,7 +16,7 @@ import static mindustry.Vars.*;
public abstract class Weather extends MappableContent{
/** Default duration of this weather event in ticks. */
public float duration = 15f * Time.toMinutes;
public float duration = 9f * Time.toMinutes;
public Attributes attrs = new Attributes();
//internals
@@ -110,7 +111,7 @@ public abstract class Weather extends MappableContent{
/** Creates a weather entry with some approximate weather values. */
public WeatherEntry(Weather weather){
this(weather, weather.duration/2f, weather.duration * 1.5f, weather.duration/2f, weather.duration * 1.5f);
this(weather, weather.duration * 1f, weather.duration * 3f, weather.duration / 2f, weather.duration * 1.5f);
}
public WeatherEntry(Weather weather, float minFrequency, float maxFrequency, float minDuration, float maxDuration){
@@ -136,6 +137,7 @@ public abstract class Weather extends MappableContent{
Weather weather;
float intensity = 1f, opacity = 0f, life, effectTimer;
Vec2 windVector = new Vec2();
void init(Weather weather){
this.weather = weather;

View File

@@ -28,11 +28,8 @@ public class CoreItemsDisplay extends Table{
update(() -> {
CoreBuild core = Vars.player.team().core();
for(Item item : content.items()){
if(core != null && core.items.get(item) > 0 && usedItems.add(item)){
rebuild();
break;
}
if(content.items().contains(item -> core != null && core.items.get(item) > 0 && usedItems.add(item))){
rebuild();
}
});

View File

@@ -15,8 +15,8 @@ import mindustry.ui.Links.*;
import static mindustry.Vars.*;
public class AboutDialog extends BaseDialog{
private Seq<String> contributors = new Seq<>();
private static ObjectSet<String> bannedItems = ObjectSet.with("google-play", "itch.io", "dev-builds", "f-droid");
Seq<String> contributors = new Seq<>();
static ObjectSet<String> bannedItems = ObjectSet.with("google-play", "itch.io", "dev-builds", "f-droid");
public AboutDialog(){
super("@about.button");

View File

@@ -8,7 +8,7 @@ import mindustry.graphics.*;
public class ColorPicker extends BaseDialog{
private Cons<Color> cons = c -> {};
private Color current = new Color();
Color current = new Color();
public ColorPicker(){
super("@pickcolor");

View File

@@ -20,7 +20,7 @@ import static mindustry.Vars.*;
public class CustomRulesDialog extends BaseDialog{
private Table main;
private Rules rules;
Rules rules;
private Prov<Rules> resetter;
private LoadoutDialog loadoutDialog;
private BaseDialog banDialog;

View File

@@ -56,7 +56,7 @@ public class DatabaseDialog extends BaseDialog{
for(int i = 0; i < array.size; i++){
UnlockableContent unlock = (UnlockableContent)array.get(i);
Image image = unlocked(unlock) ? new Image(unlock.icon(Cicon.medium)) : new Image(Icon.lockOpen, Pal.gray);
Image image = unlocked(unlock) ? new Image(unlock.icon(Cicon.medium)) : new Image(Icon.lock, Pal.gray);
list.add(image).size(8*4).pad(3);
ClickListener listener = new ClickListener();
image.addListener(listener);

View File

@@ -17,10 +17,10 @@ import java.util.*;
public class FileChooser extends BaseDialog{
private static final Fi homeDirectory = Core.files.absolute(Core.files.getExternalStoragePath());
private static Fi lastDirectory = homeDirectory;
static Fi lastDirectory = homeDirectory;
private Table files;
private Fi directory = lastDirectory;
Fi directory = lastDirectory;
private ScrollPane pane;
private TextField navigation, filefield;
private TextButton ok;
@@ -163,7 +163,7 @@ public class FileChooser extends BaseDialog{
return handles;
}
private void updateFiles(boolean push){
void updateFiles(boolean push){
if(push) stack.push(directory);
navigation.setText(directory.toString());

View File

@@ -1,6 +1,7 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.graphics.*;
import arc.input.*;
import arc.math.*;
import arc.scene.ui.*;
@@ -233,6 +234,10 @@ public class JoinDialog extends BaseDialog{
t.add("[lightgray]" + (Core.bundle.format("players" + (host.players == 1 && host.playerLimit <= 0 ? ".single" : ""), (host.players == 0 ? "[lightgray]" : "[accent]") + host.players + (host.playerLimit > 0 ? "[lightgray]/[accent]" + host.playerLimit : "")+ "[lightgray]"))).left();
t.row();
t.add("[lightgray]" + Core.bundle.format("save.map", host.mapname) + "[lightgray] / " + (host.modeName == null ? host.mode.toString() : host.modeName)).width(targetWidth() - 10f).left().get().setEllipsis(true);
if(host.ping > 0){
t.row();
t.add(Iconc.chartBar + " " + host.ping + "ms").color(Color.gray).left();
}
}).expand().left().bottom().padLeft(12f).padBottom(8);
}
@@ -434,9 +439,13 @@ public class JoinDialog extends BaseDialog{
defaultServers.clear();
val.asArray().each(child -> defaultServers.add(child.getString("address", "<invalid>")));
Log.info("Fetched @ global servers.", defaultServers.size);
}catch(Throwable ignored){}
}catch(Throwable ignored){
Log.err("Failed to parse community servers.");
}
});
}catch(Throwable ignored){}
}catch(Throwable e){
Log.err("Failed to fetch community servers.");
}
}, t -> {});
}

View File

@@ -3,6 +3,7 @@ package mindustry.ui.dialogs;
import arc.*;
import arc.Net.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.ui.TextButton.*;
@@ -106,31 +107,13 @@ public class ModsDialog extends BaseDialog{
Core.settings.put("lastmod", text);
ui.loadfrag.show();
Core.net.httpGet("http://api.github.com/repos/" + text + "/zipball/master", loc -> {
Core.net.httpGet(loc.getHeader("Location"), result -> {
if(result.getStatus() != HttpStatus.OK){
ui.showErrorMessage(Core.bundle.format("connectfail", result.getStatus()));
ui.loadfrag.hide();
}else{
try{
Fi file = tmpDirectory.child(text.replace("/", "") + ".zip");
Streams.copy(result.getResultAsStream(), file.write(false));
mods.importMod(file);
file.delete();
Core.app.post(() -> {
try{
setup();
ui.loadfrag.hide();
}catch(Throwable e){
ui.showException(e);
}
});
}catch(Throwable e){
modError(e);
}
}
}, t2 -> Core.app.post(() -> modError(t2)));
}, t2 -> Core.app.post(() -> modError(t2)));
// Try to download the 6.0 branch first, but if it doesnt exist try master.
githubImport("6.0", text, e1 -> {
githubImport("master", text, e2 -> {
ui.showErrorMessage(Core.bundle.format("connectfail", e2));
ui.loadfrag.hide();
});
});
});
}).margin(12f);
});
@@ -178,8 +161,22 @@ public class ModsDialog extends BaseDialog{
border(Pal.accent);
}}).size(h - 8f).padTop(-8f).padLeft(-8f).padRight(8f);
title.add("" + mod.meta.displayName() + "\n[lightgray]v" + mod.meta.version + (mod.enabled() ? "" : "\n" + Core.bundle.get("mod.disabled") + ""))
.wrap().top().width(170f).growX().left();
title.table(text -> {
text.add("" + mod.meta.displayName() + "\n[lightgray]v" + mod.meta.version + (mod.enabled() ? "" : "\n" + Core.bundle.get("mod.disabled") + ""))
.wrap().top().width(300f).growX().left();
text.row();
if(!mod.isSupported()){
text.labelWrap(Core.bundle.format("mod.requiresversion", mod.meta.minGameVersion)).growX();
text.row();
}else if(mod.hasUnmetDependencies()){
text.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
t.row();
}else if(mod.hasContentErrors()){
text.labelWrap("@mod.erroredcontent").growX();
text.row();
}
}).top().growX();
title.add().growX();
}).growX().growY().left();
@@ -210,17 +207,7 @@ public class ModsDialog extends BaseDialog{
}
}).growX().right().padRight(-8f).padTop(-8f);
t.row();
if(!mod.isSupported()){
t.labelWrap(Core.bundle.format("mod.requiresversion", mod.meta.minGameVersion)).growX();
t.row();
}else if(mod.hasUnmetDependencies()){
t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
t.row();
}else if(mod.hasContentErrors()){
t.labelWrap("@mod.erroredcontent").growX();
t.row();
}
}, Styles.clearPartialt, () -> showMod(mod)).size(w, h).growX().pad(4f);
table.row();
}
@@ -288,8 +275,33 @@ public class ModsDialog extends BaseDialog{
}*/
}).width(400f);
dialog.show();
}
private void githubImport(String branch, String repo, Cons<HttpStatus> err){
Core.net.httpGet("http://api.github.com/repos/" + repo + "/zipball/" + branch, loc -> {
Core.net.httpGet(loc.getHeader("Location"), result -> {
if(result.getStatus() != HttpStatus.OK){
err.get(result.getStatus());
}else{
try{
Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip");
Streams.copy(result.getResultAsStream(), file.write(false));
mods.importMod(file);
file.delete();
Core.app.post(() -> {
try{
setup();
ui.loadfrag.hide();
}catch(Throwable e){
ui.showException(e);
}
});
}catch(Throwable e){
modError(e);
}
}
}, t2 -> Core.app.post(() -> modError(t2)));
}, t2 -> Core.app.post(() -> modError(t2)));
}
}

View File

@@ -44,16 +44,7 @@ public class PausedDialog extends BaseDialog{
float dw = 220f;
cont.defaults().width(dw).height(55).pad(5f);
cont.button("@back", Icon.left, this::hide).colspan(2).width(dw * 2 + 20f);
cont.row();
//if(state.isCampaign()){
// cont.button("@techtree", Icon.tree, ui.tech::show);
//}else{
// cont.button("@database", Icon.book, ui.database::show);
//}
//TODO remove
cont.button("nothing", Icon.warning, () -> ui.showInfo("no"));
cont.button("@back", Icon.left, this::hide);
cont.button("@settings", Icon.settings, ui.settings::show);
if(!state.rules.tutorial){
@@ -93,6 +84,18 @@ public class PausedDialog extends BaseDialog{
cont.row();
cont.buttonRow("@load", Icon.download, load::show).disabled(b -> net.active());
}else if(state.isCampaign()){
cont.buttonRow("@launchcore", Icon.up, () -> {
hide();
ui.planet.show(state.getSector(), player.team().core());
}).disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements));
cont.row();
cont.buttonRow("@planetmap", Icon.map, () -> {
hide();
ui.planet.show();
});
}else{
cont.row();
}

View File

@@ -30,15 +30,15 @@ import static mindustry.ui.dialogs.PlanetDialog.Mode.*;
public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
private final FrameBuffer buffer = new FrameBuffer(2, 2, true);
private final PlanetRenderer planets = renderer.planets;
final PlanetRenderer planets = renderer.planets;
private final LaunchLoadoutDialog loadouts = new LaunchLoadoutDialog();
private final Table stable = new Table().background(Styles.black3);
private int launchRange;
private float zoom = 1f, selectAlpha = 1f;
private @Nullable Sector selected, hovered, launchSector;
@Nullable Sector selected, hovered, launchSector;
private CoreBuild launcher;
private Mode mode = look;
Mode mode = look;
private boolean launching;
public PlanetDialog(){
@@ -291,7 +291,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
//TODO localize
private void updateSelected(){
void updateSelected(){
Sector sector = selected;
if(sector == null){

View File

@@ -32,14 +32,14 @@ import java.util.*;
import static mindustry.Vars.*;
public class ResearchDialog extends BaseDialog{
private final float nodeSize = Scl.scl(60f);
private ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
private TechTreeNode root = new TechTreeNode(TechTree.root, null);
private Rect bounds = new Rect();
private ItemsDisplay itemDisplay;
private View view;
final float nodeSize = Scl.scl(60f);
ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
TechTreeNode root = new TechTreeNode(TechTree.root, null);
Rect bounds = new Rect();
ItemsDisplay itemDisplay;
View view;
private ItemSeq items;
ItemSeq items;
public ResearchDialog(){
super("");
@@ -134,7 +134,7 @@ public class ResearchDialog extends BaseDialog{
}
});
addListener(new ElementGestureListener(){
view.addListener(new ElementGestureListener(){
@Override
public void zoom(InputEvent event, float initialDistance, float distance){
if(view.lastZoom < 0){
@@ -233,7 +233,7 @@ public class ResearchDialog extends BaseDialog{
}
boolean selectable(TechNode node){
return !node.objectives.contains(i -> !i.complete());
return node.content.unlocked() || !node.objectives.contains(i -> !i.complete());
}
void showToast(String info){

View File

@@ -79,7 +79,7 @@ public class SchematicsDialog extends BaseDialog{
t.clear();
int i = 0;
String regex = "[`~!@#$%^&*()-_=+[{]}|;:'\",<.>/?]";
String regex = "[`~!@#$%^&*()-_=+{}|;:'\",<.>/?]";
String searchString = search.toLowerCase().replaceAll(regex, " ");
firstSchematic = null;

View File

@@ -12,8 +12,8 @@ import mindustry.gen.*;
import static mindustry.Vars.*;
public class BlockConfigFragment extends Fragment{
private Table table = new Table();
private Building configTile;
Table table = new Table();
Building configTile;
@Override
public void build(Group parent){

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