Merge branch 'master' of https://github.com/Anuken/Mindustry into lights

# Conflicts:
#	core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java
This commit is contained in:
Anuken
2019-11-10 14:12:43 -05:00
332 changed files with 18151 additions and 10448 deletions

View File

@@ -9,8 +9,9 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.async.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
@@ -81,6 +82,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
add(netClient = new NetClient());
assets.load(mods);
assets.load(schematics);
assets.loadRun("contentinit", ContentLoader.class, () -> {
content.init();
@@ -118,10 +120,11 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
for(ApplicationListener listener : modules){
listener.init();
}
super.resize(graphics.getWidth(), graphics.getHeight());
mods.each(Mod::init);
finished = true;
Events.fire(new ClientLoadEvent());
super.resize(graphics.getWidth(), graphics.getHeight());
app.post(() -> app.post(() -> app.post(() -> app.post(() -> super.resize(graphics.getWidth(), graphics.getHeight())))));
}
}else{
super.update();
@@ -133,11 +136,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
long target = (1000 * 1000000) / targetfps; //target in nanos
long elapsed = Time.timeSinceNanos(lastTime);
if(elapsed < target){
try{
Thread.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000));
}catch(InterruptedException ignored){
//ignore
}
Threads.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000));
}
}

View File

@@ -33,6 +33,10 @@ public class Vars implements Loadable{
public static boolean loadLocales = true;
/** Maximum number of broken blocks. TODO implement or remove.*/
public static final int maxBrokenBlocks = 256;
/** Maximum schematic size.*/
public static final int maxSchematicSize = 32;
/** All schematic base64 starts with this string.*/
public static final String schematicBaseStart ="bXNjaAB";
/** IO buffer size. */
public static final int bufferSize = 8192;
/** global charset, since Android doesn't support the Charsets class */
@@ -128,10 +132,14 @@ public class Vars implements Loadable{
public static FileHandle saveDirectory;
/** data subdirectory used for mods */
public static FileHandle modDirectory;
/** data subdirectory used for schematics */
public static FileHandle schematicDirectory;
/** map file extension */
public static final String mapExtension = "msav";
/** save file extension */
public static final String saveExtension = "msav";
/** schematic file extension */
public static final String schematicExtension = "msch";
/** list of all locales that can be switched to */
public static Locale[] locales;
@@ -146,6 +154,7 @@ public class Vars implements Loadable{
public static LoopControl loops;
public static Platform platform = new Platform(){};
public static Mods mods;
public static Schematics schematics = new Schematics();
public static World world;
public static Maps maps;
@@ -251,6 +260,7 @@ public class Vars implements Loadable{
saveDirectory = dataDirectory.child("saves/");
tmpDirectory = dataDirectory.child("tmp/");
modDirectory = dataDirectory.child("mods/");
schematicDirectory = dataDirectory.child("schematics/");
modDirectory.mkdirs();
@@ -261,7 +271,7 @@ public class Vars implements Loadable{
public static void loadSettings(){
Core.settings.setAppName(appName);
if(steam || "steam".equals(Version.modifier)){
if(steam || (Version.modifier != null && Version.modifier.contains("steam"))){
Core.settings.setDataDirectory(Core.files.local("saves/"));
}
@@ -289,7 +299,6 @@ public class Vars implements Loadable{
//no external bundle found
FileHandle handle = Core.files.internal("bundles/bundle");
Locale locale;
String loc = Core.settings.getString("locale");
if(loc.equals("default")){

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.ai;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.mindustry.content.*;
@@ -27,7 +27,7 @@ public class BlockIndexer{
private final ObjectSet<Item> scanOres = new ObjectSet<>();
private final ObjectSet<Item> itemSet = new ObjectSet<>();
/** Stores all ore quadtrants on the map. */
private ObjectMap<Item, ObjectSet<Tile>> ores;
private ObjectMap<Item, ObjectSet<Tile>> ores = new ObjectMap<>();
/** Tags all quadrants. */
private GridBits[] structQuadrants;
/** Stores all damaged tile entities by team. */
@@ -163,7 +163,11 @@ public class BlockIndexer{
set.add(entity.tile);
}
public TileEntity findTile(Team team, float x, float y, float range, Predicate<Tile> pred){
public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred){
return findTile(team, x, y, range, pred, false);
}
public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred, boolean usePriority){
TileEntity closest = null;
float dst = 0;
@@ -178,13 +182,13 @@ public class BlockIndexer{
if(other == null) continue;
if(other.entity == null || other.getTeam() != team || !pred.test(other) || !other.block().targetable)
if(other.entity == null || other.getTeam() != team || !pred.get(other) || !other.block().targetable)
continue;
TileEntity e = other.entity;
float ndst = Mathf.dst(x, y, e.x, e.y);
if(ndst < range && (closest == null || ndst < dst)){
if(ndst < range && (closest == null || ndst < dst || (usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
dst = ndst;
closest = e;
}

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.ai;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
@@ -317,15 +317,15 @@ public class Pathfinder implements Runnable{
public static final PathTarget[] all = values();
private final BiConsumer<Team, IntArray> targeter;
private final Cons2<Team, IntArray> targeter;
PathTarget(BiConsumer<Team, IntArray> targeter){
PathTarget(Cons2<Team, IntArray> targeter){
this.targeter = targeter;
}
/** Get targets. This must run on the main thread.*/
public IntArray getTargets(Team team, IntArray out){
targeter.accept(team, out);
targeter.get(team, out);
return out;
}
}

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.ai;
import io.anuke.arc.Events;
import io.anuke.arc.collection.Array;
import io.anuke.arc.function.PositionConsumer;
import io.anuke.arc.func.Floatc2;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Time;
@@ -99,17 +99,17 @@ public class WaveSpawner{
}
}
private void eachFlyerSpawn(PositionConsumer cons){
private void eachFlyerSpawn(Floatc2 cons){
for(FlyerSpawn spawn : flySpawns){
float trns = (world.width() + world.height()) * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(spawn.angle, trns), -margin, world.width() * tilesize + margin);
float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(spawn.angle, trns), -margin, world.height() * tilesize + margin);
cons.accept(spawnX, spawnY);
cons.get(spawnX, spawnY);
}
if(state.rules.attackMode && state.teams.isActive(waveTeam)){
for(Tile core : state.teams.get(waveTeam).cores){
cons.accept(core.worldx(), core.worldy());
cons.get(core.worldx(), core.worldy());
}
}
}

View File

@@ -7,10 +7,10 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
@@ -29,8 +29,6 @@ import io.anuke.mindustry.world.consumers.*;
import io.anuke.mindustry.world.meta.*;
import io.anuke.mindustry.world.modules.*;
import static io.anuke.mindustry.Vars.*;
public class Blocks implements ContentList{
public static Block
@@ -54,11 +52,11 @@ public class Blocks implements ContentList{
//defense
scrapWall, scrapWallLarge, scrapWallHuge, scrapWallGigantic, thruster, //ok, these names are getting ridiculous, but at least I don't have humongous walls yet
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
phaseWall, phaseWallLarge, surgeWall, surgeWallLarge, mender, mendProjector, overdriveProjector, forceProjector, shockMine,
//transport
conveyor, titaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, router, overflowGate, massDriver,
conveyor, titaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, invertedSorter, router, overflowGate, massDriver,
//liquids
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
@@ -716,23 +714,23 @@ public class Blocks implements ContentList{
//region sandbox
powerVoid = new PowerVoid("power-void"){{
requirements(Category.power, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.power, BuildVisibility.sandboxOnly, ItemStack.with());
alwaysUnlocked = true;
}};
powerSource = new PowerSource("power-source"){{
requirements(Category.power, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.power, BuildVisibility.sandboxOnly, ItemStack.with());
alwaysUnlocked = true;
}};
itemSource = new ItemSource("item-source"){{
requirements(Category.distribution, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.distribution, BuildVisibility.sandboxOnly, ItemStack.with());
alwaysUnlocked = true;
}};
itemVoid = new ItemVoid("item-void"){{
requirements(Category.distribution, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.distribution, BuildVisibility.sandboxOnly, ItemStack.with());
alwaysUnlocked = true;
}};
liquidSource = new LiquidSource("liquid-source"){{
requirements(Category.liquid, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.liquid, BuildVisibility.sandboxOnly, ItemStack.with());
alwaysUnlocked = true;
}};
message = new MessageBlock("message"){{
@@ -745,27 +743,27 @@ public class Blocks implements ContentList{
int wallHealthMultiplier = 4;
scrapWall = new Wall("scrap-wall"){{
requirements(Category.defense, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with());
health = 60 * wallHealthMultiplier;
variants = 5;
}};
scrapWallLarge = new Wall("scrap-wall-large"){{
requirements(Category.defense, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with());
health = 60 * 4 * wallHealthMultiplier;
size = 2;
variants = 4;
}};
scrapWallHuge = new Wall("scrap-wall-huge"){{
requirements(Category.defense, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with());
health = 60 * 9 * wallHealthMultiplier;
size = 3;
variants = 3;
}};
scrapWallGigantic = new Wall("scrap-wall-gigantic"){{
requirements(Category.defense, () -> state.rules.infiniteResources, ItemStack.with());
requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with());
health = 60 * 16 * wallHealthMultiplier;
size = 4;
}};
@@ -797,6 +795,19 @@ public class Blocks implements ContentList{
size = 2;
}};
plastaniumWall = new Wall("plastanium-wall"){{
requirements(Category.defense, ItemStack.with(Items.plastanium, 5, Items.metaglass, 2));
health = 190 * wallHealthMultiplier;
insulated = true;
}};
plastaniumWallLarge = new Wall("plastanium-wall-large"){{
requirements(Category.defense, ItemStack.mult(plastaniumWall.requirements, 4));
health = 190 * wallHealthMultiplier * 4;
size = 2;
insulated = true;
}};
thoriumWall = new Wall("thorium-wall"){{
requirements(Category.defense, ItemStack.with(Items.thorium, 6));
health = 200 * wallHealthMultiplier;
@@ -935,7 +946,11 @@ public class Blocks implements ContentList{
sorter = new Sorter("sorter"){{
requirements(Category.distribution, ItemStack.with(Items.lead, 2, Items.copper, 2));
}};
invertedSorter = new Sorter("inverted-sorter"){{
requirements(Category.distribution, ItemStack.with(Items.lead, 2, Items.copper, 2));
invert = true;
}};
router = new Router("router"){{
@@ -965,12 +980,12 @@ public class Blocks implements ContentList{
//region liquid
mechanicalPump = new Pump("mechanical-pump"){{
requirements(Category.liquid, ItemStack.with(Items.copper, 15, Items.lead, 10));
requirements(Category.liquid, ItemStack.with(Items.copper, 15, Items.metaglass, 10));
pumpAmount = 0.1f;
}};
rotaryPump = new Pump("rotary-pump"){{
requirements(Category.liquid, ItemStack.with(Items.copper, 70, Items.lead, 50, Items.silicon, 20, Items.titanium, 35));
requirements(Category.liquid, ItemStack.with(Items.copper, 70, Items.metaglass, 50, Items.silicon, 20, Items.titanium, 35));
pumpAmount = 0.8f;
consumes.power(0.15f);
liquidCapacity = 30f;
@@ -979,7 +994,7 @@ public class Blocks implements ContentList{
}};
thermalPump = new Pump("thermal-pump"){{
requirements(Category.liquid, ItemStack.with(Items.copper, 80, Items.lead, 65, Items.silicon, 30, Items.titanium, 40, Items.thorium, 35));
requirements(Category.liquid, ItemStack.with(Items.copper, 80, Items.metaglass, 70, Items.silicon, 30, Items.titanium, 40, Items.thorium, 35));
pumpAmount = 1.5f;
consumes.power(0.30f);
liquidCapacity = 40f;
@@ -993,13 +1008,13 @@ public class Blocks implements ContentList{
}};
pulseConduit = new Conduit("pulse-conduit"){{
requirements(Category.liquid, ItemStack.with(Items.titanium, 1, Items.metaglass, 1));
requirements(Category.liquid, ItemStack.with(Items.titanium, 2, Items.metaglass, 1));
liquidCapacity = 16f;
health = 90;
}};
liquidRouter = new LiquidRouter("liquid-router"){{
requirements(Category.liquid, ItemStack.with(Items.titanium, 2, Items.metaglass, 2));
requirements(Category.liquid, ItemStack.with(Items.graphite, 4, Items.metaglass, 2));
liquidCapacity = 20f;
}};
@@ -1011,11 +1026,11 @@ public class Blocks implements ContentList{
}};
liquidJunction = new LiquidJunction("liquid-junction"){{
requirements(Category.liquid, ItemStack.with(Items.titanium, 2, Items.metaglass, 2));
requirements(Category.liquid, ItemStack.with(Items.graphite, 2, Items.metaglass, 2));
}};
bridgeConduit = new LiquidExtendingBridge("bridge-conduit"){{
requirements(Category.liquid, ItemStack.with(Items.titanium, 4, Items.metaglass, 4));
requirements(Category.liquid, ItemStack.with(Items.graphite, 4, Items.metaglass, 8));
range = 4;
hasPower = false;
}};
@@ -1083,11 +1098,12 @@ public class Blocks implements ContentList{
size = 2;
}};
differentialGenerator = new SingleTypeGenerator(true, false, "differential-generator"){{
differentialGenerator = new SingleTypeGenerator("differential-generator"){{
requirements(Category.power, ItemStack.with(Items.copper, 70, Items.titanium, 50, Items.lead, 100, Items.silicon, 65, Items.metaglass, 50));
powerProduction = 16f;
itemDuration = 120f;
hasLiquids = true;
hasItems = true;
size = 3;
consumes.item(Items.pyratite).optional(true, false);
@@ -1230,7 +1246,7 @@ public class Blocks implements ContentList{
//region storage
coreShard = new CoreBlock("core-shard"){{
requirements(Category.effect, () -> false, ItemStack.with(Items.titanium, 1000));
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with(Items.titanium, 4000));
alwaysUnlocked = true;
health = 1100;
@@ -1239,7 +1255,7 @@ public class Blocks implements ContentList{
}};
coreFoundation = new CoreBlock("core-foundation"){{
requirements(Category.effect, () -> false, ItemStack.with(Items.titanium, 1500, Items.silicon, 1000));
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with(Items.titanium, 400, Items.silicon, 3000));
health = 2000;
itemCapacity = 9000;
@@ -1247,7 +1263,7 @@ public class Blocks implements ContentList{
}};
coreNucleus = new CoreBlock("core-nucleus"){{
requirements(Category.effect, () -> false, ItemStack.with(Items.titanium, 4000, Items.silicon, 2000, Items.surgealloy, 1000));
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with(Items.titanium, 4000, Items.silicon, 2000, Items.surgealloy, 3000));
health = 4000;
itemCapacity = 13000;
@@ -1272,7 +1288,7 @@ public class Blocks implements ContentList{
}};
launchPad = new LaunchPad("launch-pad"){{
requirements(Category.effect, () -> world.isZone(), ItemStack.with(Items.copper, 250, Items.silicon, 75, Items.lead, 100));
requirements(Category.effect, BuildVisibility.campaignOnly, ItemStack.with(Items.copper, 250, Items.silicon, 75, Items.lead, 100));
size = 3;
itemCapacity = 100;
launchTime = 60f * 16;
@@ -1281,7 +1297,7 @@ public class Blocks implements ContentList{
}};
launchPadLarge = new LaunchPad("launch-pad-large"){{
requirements(Category.effect, () -> world.isZone(), ItemStack.with(Items.titanium, 200, Items.silicon, 150, Items.lead, 250, Items.plastanium, 75));
requirements(Category.effect, BuildVisibility.campaignOnly, ItemStack.with(Items.titanium, 200, Items.silicon, 150, Items.lead, 250, Items.plastanium, 75));
size = 4;
itemCapacity = 250;
launchTime = 60f * 14;
@@ -1341,7 +1357,8 @@ public class Blocks implements ContentList{
Items.pyratite, Bullets.pyraFlame
);
recoil = 0f;
reload = 4f;
reload = 5f;
coolantMultiplier = 2f;
range = 60f;
shootCone = 50f;
targetAir = false;
@@ -1612,7 +1629,7 @@ public class Blocks implements ContentList{
size = 4;
shootShake = 2f;
range = 190f;
reload = 50f;
reload = 80f;
firingMoveFract = 0.5f;
shootDuration = 220f;
powerUse = 14f;
@@ -1629,7 +1646,7 @@ public class Blocks implements ContentList{
draugFactory = new UnitFactory("draug-factory"){{
requirements(Category.units, ItemStack.with(Items.copper, 30, Items.lead, 70));
type = UnitTypes.draug;
unitType = UnitTypes.draug;
produceTime = 2500;
size = 2;
maxSpawn = 1;
@@ -1639,7 +1656,7 @@ public class Blocks implements ContentList{
spiritFactory = new UnitFactory("spirit-factory"){{
requirements(Category.units, ItemStack.with(Items.metaglass, 45, Items.lead, 55, Items.silicon, 45));
type = UnitTypes.spirit;
unitType = UnitTypes.spirit;
produceTime = 4000;
size = 2;
maxSpawn = 1;
@@ -1649,7 +1666,7 @@ public class Blocks implements ContentList{
phantomFactory = new UnitFactory("phantom-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 50, Items.thorium, 60, Items.lead, 65, Items.silicon, 105));
type = UnitTypes.phantom;
unitType = UnitTypes.phantom;
produceTime = 4400;
size = 2;
maxSpawn = 1;
@@ -1666,7 +1683,7 @@ public class Blocks implements ContentList{
wraithFactory = new UnitFactory("wraith-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 30, Items.lead, 40, Items.silicon, 45));
type = UnitTypes.wraith;
unitType = UnitTypes.wraith;
produceTime = 700;
size = 2;
consumes.power(0.5f);
@@ -1675,7 +1692,7 @@ public class Blocks implements ContentList{
ghoulFactory = new UnitFactory("ghoul-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 75, Items.lead, 65, Items.silicon, 110));
type = UnitTypes.ghoul;
unitType = UnitTypes.ghoul;
produceTime = 1150;
size = 3;
consumes.power(1.2f);
@@ -1684,7 +1701,7 @@ public class Blocks implements ContentList{
revenantFactory = new UnitFactory("revenant-factory"){{
requirements(Category.units, ItemStack.with(Items.plastanium, 50, Items.titanium, 150, Items.lead, 150, Items.silicon, 200));
type = UnitTypes.revenant;
unitType = UnitTypes.revenant;
produceTime = 2000;
size = 4;
consumes.power(3f);
@@ -1693,7 +1710,7 @@ public class Blocks implements ContentList{
daggerFactory = new UnitFactory("dagger-factory"){{
requirements(Category.units, ItemStack.with(Items.lead, 55, Items.silicon, 35));
type = UnitTypes.dagger;
unitType = UnitTypes.dagger;
produceTime = 850;
size = 2;
consumes.power(0.5f);
@@ -1702,7 +1719,7 @@ public class Blocks implements ContentList{
crawlerFactory = new UnitFactory("crawler-factory"){{
requirements(Category.units, ItemStack.with(Items.lead, 45, Items.silicon, 30));
type = UnitTypes.crawler;
unitType = UnitTypes.crawler;
produceTime = 300;
size = 2;
maxSpawn = 6;
@@ -1712,7 +1729,7 @@ public class Blocks implements ContentList{
titanFactory = new UnitFactory("titan-factory"){{
requirements(Category.units, ItemStack.with(Items.graphite, 50, Items.lead, 50, Items.silicon, 45));
type = UnitTypes.titan;
unitType = UnitTypes.titan;
produceTime = 1050;
size = 3;
consumes.power(0.60f);
@@ -1721,7 +1738,7 @@ public class Blocks implements ContentList{
fortressFactory = new UnitFactory("fortress-factory"){{
requirements(Category.units, ItemStack.with(Items.thorium, 40, Items.lead, 110, Items.silicon, 75));
type = UnitTypes.fortress;
unitType = UnitTypes.fortress;
produceTime = 2000;
size = 3;
maxSpawn = 3;

View File

@@ -4,11 +4,11 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
@@ -99,8 +99,7 @@ public class Bullets implements ContentList{
collidesTiles = false;
splashDamageRadius = 25f;
splashDamage = 30f;
incendAmount = 4;
incendSpread = 11f;
status = StatusEffects.burning;
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
trailEffect = Fx.incendTrail;
@@ -228,8 +227,7 @@ public class Bullets implements ContentList{
splashDamage = 10f;
lifetime = 160f;
hitEffect = Fx.blastExplosion;
incendSpread = 10f;
incendAmount = 3;
status = StatusEffects.burning;
}};
missileSurge = new MissileBulletType(4.4f, 15, "bullet"){{
@@ -342,9 +340,7 @@ public class Bullets implements ContentList{
bulletHeight = 12f;
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
incendSpread = 3f;
incendAmount = 1;
incendChance = 0.3f;
status = StatusEffects.burning;
inaccuracy = 3f;
lifetime = 60f;
}};
@@ -354,9 +350,7 @@ public class Bullets implements ContentList{
bulletHeight = 12f;
frontColor = Color.valueOf("feb380");
backColor = Color.valueOf("ea8878");
incendSpread = 3f;
incendAmount = 1;
incendChance = 0.3f;
status = StatusEffects.burning;
lifetime = 60f;
}};
@@ -385,9 +379,7 @@ public class Bullets implements ContentList{
bulletHeight = 21f;
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
incendSpread = 3f;
incendAmount = 2;
incendChance = 0.3f;
status = StatusEffects.burning;
shootEffect = Fx.shootBig;
}};

View File

@@ -5,12 +5,13 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.effect.GroundEffectEntity.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.Cicon;
import static io.anuke.mindustry.Vars.tilesize;

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.content;
import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemType;

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.content;
import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.type.Liquid;
public class Liquids implements ContentList{
@@ -26,6 +26,7 @@ public class Liquids implements ContentList{
flammability = 1.2f;
explosiveness = 1.2f;
heatCapacity = 0.7f;
barColor = Color.valueOf("6b675f");
effect = StatusEffects.tarred;
}};

View File

@@ -1,10 +1,12 @@
package io.anuke.mindustry.content;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.type.Loadout;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.game.*;
import java.io.*;
public class Loadouts implements ContentList{
public static Loadout
public static Schematic
basicShard,
advancedShard,
basicFoundation,
@@ -12,43 +14,13 @@ public class Loadouts implements ContentList{
@Override
public void load(){
basicShard = new Loadout(
" ### ",
" #1# ",
" ### ",
" ^ ^ ",
" ## ## ",
" C# C# "
);
advancedShard = new Loadout(
" ### ",
" #1# ",
"#######",
"C#^ ^C#",
" ## ## ",
" C# C# "
);
basicFoundation = new Loadout(
" #### ",
" #### ",
" #2## ",
" #### ",
" ^^^^ ",
" ###### ",
" C#C#C# "
);
basicNucleus = new Loadout(
" ##### ",
" ##### ",
" ##3## ",
" ##### ",
" >#####< ",
" ^ ^ ^ ^ ",
"#### ####",
"C#C# C#C#"
);
try{
basicShard = Schematics.readBase64("bXNjaAB4nD2K2wqAIBiD5ymibnoRn6YnEP1BwUMoBL19FuJ2sbFvUFgYZDaJsLeQrkinN9UJHImsNzlYE7WrIUastuSbnlKx2VJJt+8IQGGKdfO/8J5yrGJSMegLg+YUIA==");
advancedShard = Schematics.readBase64("bXNjaAB4nD2LjQqAIAyET7OMIOhFfJqeYMxBgSkYCL199gu33fFtB4tOwUTaBCP5QpHFzwtl32DahBeKK1NwPq8hoOcUixwpY+CUxe3XIwBbB/pa6tadVCUP02hgHvp5vZq/0b7pBHPYFOQ=");
basicFoundation = Schematics.readBase64("bXNjaAB4nD1OSQ6DMBBzFhVu8BG+0X8MQyoiJTNSukj8nlCi2Adbtg/GA4OBF8oB00rvyE/9ykafqOIw58A7SWRKy1ZiShhZ5RcOLZhYS1hefQ1gRIeptH9jq/qW2lvc1d2tgWsOfVX/tOwE86AYBA==");
basicNucleus = Schematics.readBase64("bXNjaAB4nD2MUQqAIBBEJy0s6qOLdJXuYNtCgikYBd2+LNmdj308hkGHtkId7M4YFns4mk/yfB4a48602eDI+mlNznu0FMPFd0wYKCaewl8F0EOueqM+yKSLVfJrNKWnSw/FZGzEGXFG9sy/px4gEBW1");
}catch(IOException e){
throw new RuntimeException(e);
}
}
}

View File

@@ -6,11 +6,11 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
@@ -38,7 +38,7 @@ public class Mechs implements ContentList{
weapon = new Weapon("blaster"){{
length = 1.5f;
reload = 14f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardMechSmall;
}};
@@ -71,7 +71,7 @@ public class Mechs implements ContentList{
length = 1f;
reload = 55f;
shotDelay = 3f;
roundrobin = true;
alternate = true;
shots = 2;
inaccuracy = 0f;
ejectEffect = Fx.none;
@@ -116,7 +116,7 @@ public class Mechs implements ContentList{
weapon = new Weapon("heal-blaster"){{
length = 1.5f;
reload = 24f;
roundrobin = false;
alternate = false;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBullet;
@@ -168,7 +168,7 @@ public class Mechs implements ContentList{
shots = 4;
spacing = 8f;
inaccuracy = 8f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
shake = 3f;
bullet = Bullets.missileSwarm;
@@ -194,7 +194,7 @@ public class Mechs implements ContentList{
@Override
public void updateAlt(Player player){
float scl = 1f - player.shootHeat / 2f;
float scl = 1f - player.shootHeat / 2f*Time.delta();
player.velocity().scl(scl);
}
@@ -232,7 +232,7 @@ public class Mechs implements ContentList{
weapon = new Weapon("blaster"){{
length = 1.5f;
reload = 15f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}};
@@ -262,7 +262,7 @@ public class Mechs implements ContentList{
reload = 70f;
shots = 4;
inaccuracy = 2f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 0.2f;
spacing = 1f;
@@ -327,7 +327,7 @@ public class Mechs implements ContentList{
shots = 2;
shotDelay = 1f;
shots = 8;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 1f;
inaccuracy = 20f;
@@ -365,7 +365,7 @@ public class Mechs implements ContentList{
weapon = new Weapon("bomber"){{
length = 1.5f;
reload = 13f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardGlaive;
shootSound = Sounds.shootSnap;

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.content;
import io.anuke.arc.*;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.type.StatusEffect;
@@ -18,7 +18,7 @@ public class StatusEffects implements ContentList{
none = new StatusEffect();
burning = new StatusEffect(){{
damage = 0.04f;
damage = 0.06f;
effect = Fx.burning;
opposite(() -> wet, () -> freezing);

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.content;
import io.anuke.arc.collection.Array;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.world.Block;
@@ -31,6 +31,7 @@ public class TechTree implements ContentList{
node(distributor);
node(sorter, () -> {
node(invertedSorter);
node(message);
node(overflowGate);
});
@@ -103,6 +104,11 @@ public class TechTree implements ContentList{
node(door, () -> {
node(doorLarge);
});
node(plastaniumWall, () -> {
node(plastaniumWallLarge, () -> {
});
});
node(titaniumWallLarge);
node(thoriumWall, () -> {
node(thoriumWallLarge);
@@ -316,9 +322,9 @@ public class TechTree implements ContentList{
return node(block, () -> {});
}
public static void create(Block parent, Block block){
public static TechNode create(Block parent, Block block){
TechNode.context = all.find(t -> t.block == parent);
node(block, () -> {});
return node(block, () -> {});
}
public static class TechNode{

View File

@@ -3,8 +3,8 @@ package io.anuke.mindustry.content;
import io.anuke.mindustry.entities.effect.Fire;
import io.anuke.mindustry.entities.effect.Puddle;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.game.TypeID;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.type.TypeID;
public class TypeIDs implements ContentList{
public static TypeID fire, puddle, player;

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.content;
import io.anuke.arc.collection.*;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.type.base.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.type.*;
@@ -41,11 +41,11 @@ public class UnitTypes implements ContentList{
health = 100;
engineSize = 1.8f;
engineOffset = 5.7f;
weapon = new Weapon("heal-blaster"){{
weapon = new Weapon(){{
length = 1.5f;
reload = 40f;
width = 0.5f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBulletBig;
@@ -62,14 +62,14 @@ public class UnitTypes implements ContentList{
range = 70f;
itemCapacity = 70;
health = 400;
buildPower = 1f;
buildPower = 0.4f;
engineOffset = 6.5f;
toMine = ObjectSet.with(Items.lead, Items.copper, Items.titanium);
weapon = new Weapon("heal-blaster"){{
weapon = new Weapon(){{
length = 1.5f;
reload = 20f;
width = 0.5f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBullet;
@@ -86,7 +86,7 @@ public class UnitTypes implements ContentList{
weapon = new Weapon("chain-blaster"){{
length = 1.5f;
reload = 28f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}};
@@ -99,7 +99,7 @@ public class UnitTypes implements ContentList{
hitsize = 8f;
mass = 1.75f;
health = 120;
weapon = new Weapon("bomber"){{
weapon = new Weapon(){{
reload = 12f;
ejectEffect = Fx.none;
shootSound = Sounds.explosion;
@@ -138,7 +138,7 @@ public class UnitTypes implements ContentList{
length = 1f;
reload = 14f;
range = 30f;
roundrobin = true;
alternate = true;
recoil = 1f;
ejectEffect = Fx.none;
bullet = Bullets.basicFlame;
@@ -158,7 +158,7 @@ public class UnitTypes implements ContentList{
length = 1f;
reload = 60f;
width = 10f;
roundrobin = true;
alternate = true;
recoil = 4f;
shake = 2f;
ejectEffect = Fx.shellEjectMedium;
@@ -180,7 +180,7 @@ public class UnitTypes implements ContentList{
weapon = new Weapon("eruption"){{
length = 3f;
reload = 10f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
bullet = Bullets.eruptorShot;
recoil = 1f;
@@ -201,7 +201,7 @@ public class UnitTypes implements ContentList{
length = 8f;
reload = 50f;
width = 17f;
roundrobin = true;
alternate = true;
recoil = 3f;
shake = 2f;
shots = 4;
@@ -225,7 +225,7 @@ public class UnitTypes implements ContentList{
length = 13f;
reload = 30f;
width = 22f;
roundrobin = true;
alternate = true;
recoil = 3f;
shake = 2f;
inaccuracy = 3f;
@@ -247,10 +247,10 @@ public class UnitTypes implements ContentList{
health = 75;
engineOffset = 5.5f;
range = 140f;
weapon = new Weapon("chain-blaster"){{
weapon = new Weapon(){{
length = 1.5f;
reload = 28f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
shootSound = Sounds.shoot;
@@ -267,11 +267,11 @@ public class UnitTypes implements ContentList{
targetAir = false;
engineOffset = 7.8f;
range = 140f;
weapon = new Weapon("bomber"){{
weapon = new Weapon(){{
length = 0f;
width = 2f;
reload = 12f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 1f;
inaccuracy = 40f;
@@ -303,7 +303,7 @@ public class UnitTypes implements ContentList{
width = 10f;
shots = 2;
inaccuracy = 2f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 0.2f;
spacing = 1f;
@@ -336,7 +336,7 @@ public class UnitTypes implements ContentList{
shootCone = 100f;
shotDelay = 2;
inaccuracy = 10f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 0.2f;
spacing = 1f;
@@ -369,7 +369,7 @@ public class UnitTypes implements ContentList{
shake = 1f;
inaccuracy = 3f;
roundrobin = true;
alternate = true;
ejectEffect = Fx.none;
bullet = new BasicBulletType(7f, 42, "bullet"){
{

View File

@@ -1,13 +1,16 @@
package io.anuke.mindustry.content;
import io.anuke.arc.collection.Array;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.game.SpawnGroup;
import io.anuke.mindustry.maps.generators.MapGenerator;
import io.anuke.mindustry.maps.generators.MapGenerator.Decoration;
import io.anuke.mindustry.maps.zonegen.DesertWastesGenerator;
import io.anuke.mindustry.ctype.ContentList;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Objectives.*;
import io.anuke.mindustry.maps.generators.*;
import io.anuke.mindustry.maps.generators.MapGenerator.*;
import io.anuke.mindustry.maps.zonegen.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.Block;
import static io.anuke.arc.collection.Array.with;
import static io.anuke.mindustry.content.Items.*;
import static io.anuke.mindustry.type.ItemStack.list;
public class Zones implements ContentList{
public static Zone
@@ -20,28 +23,26 @@ public class Zones implements ContentList{
public void load(){
groundZero = new Zone("groundZero", new MapGenerator("groundZero", 1)){{
baseLaunchCost = ItemStack.with(Items.copper, -60);
startingItems = ItemStack.list(Items.copper, 60);
baseLaunchCost = list(copper, -60);
startingItems = list(copper, 60);
alwaysUnlocked = true;
conditionWave = 5;
launchPeriod = 5;
resources = new Item[]{Items.copper, Items.scrap, Items.lead};
resources = with(copper, scrap, lead);
}};
desertWastes = new Zone("desertWastes", new DesertWastesGenerator(260, 260)){{
startingItems = ItemStack.list(Items.copper, 120);
startingItems = list(copper, 120);
conditionWave = 20;
launchPeriod = 10;
loadout = Loadouts.advancedShard;
zoneRequirements = ZoneRequirement.with(groundZero, 20);
blockRequirements = new Block[]{Blocks.combustionGenerator};
resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.sand};
resources = with(copper, lead, coal, sand);
rules = r -> {
r.waves = true;
r.waveTimer = true;
r.launchWaveMultiplier = 3f;
r.waveSpacing = 60 * 50f;
r.spawns = Array.with(
r.spawns = with(
new SpawnGroup(UnitTypes.crawler){{
unitScaling = 3f;
}},
@@ -75,96 +76,140 @@ public class Zones implements ContentList{
}}
);
};
requirements = with(
new ZoneWave(groundZero, 20),
new Unlock(Blocks.combustionGenerator)
);
}};
saltFlats = new Zone("saltFlats", new MapGenerator("saltFlats")){{
startingItems = ItemStack.list(Items.copper, 200, Items.silicon, 200, Items.lead, 200);
startingItems = list(copper, 200, Items.silicon, 200, lead, 200);
loadout = Loadouts.basicFoundation;
conditionWave = 10;
launchPeriod = 5;
zoneRequirements = ZoneRequirement.with(desertWastes, 60);
blockRequirements = new Block[]{Blocks.daggerFactory, Blocks.draugFactory, Blocks.door, Blocks.waterExtractor};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand, Items.titanium};
configureObjective = new Launched(this);
resources = with(copper, scrap, lead, coal, sand, titanium);
requirements = with(
new ZoneWave(desertWastes, 60),
new Unlock(Blocks.daggerFactory),
new Unlock(Blocks.draugFactory),
new Unlock(Blocks.door),
new Unlock(Blocks.waterExtractor)
);
}};
frozenForest = new Zone("frozenForest", new MapGenerator("frozenForest", 1)
.decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.02))){{
loadout = Loadouts.basicFoundation;
baseLaunchCost = ItemStack.with();
startingItems = ItemStack.list(Items.copper, 250);
startingItems = list(copper, 250);
conditionWave = 10;
blockRequirements = new Block[]{Blocks.junction, Blocks.router};
zoneRequirements = ZoneRequirement.with(groundZero, 10);
resources = new Item[]{Items.copper, Items.lead, Items.coal};
resources = with(copper, lead, coal);
requirements = with(
new ZoneWave(groundZero, 10),
new Unlock(Blocks.junction),
new Unlock(Blocks.router)
);
}};
craters = new Zone("craters", new MapGenerator("craters", 1).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.004))){{
startingItems = ItemStack.list(Items.copper, 100);
startingItems = list(copper, 100);
conditionWave = 10;
zoneRequirements = ZoneRequirement.with(frozenForest, 10);
blockRequirements = new Block[]{Blocks.mender, Blocks.combustionGenerator};
resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.sand, Items.scrap};
resources = with(copper, lead, coal, sand, scrap);
requirements = with(
new ZoneWave(frozenForest, 10),
new Unlock(Blocks.mender),
new Unlock(Blocks.combustionGenerator)
);
}};
ruinousShores = new Zone("ruinousShores", new MapGenerator("ruinousShores", 1)){{
loadout = Loadouts.basicFoundation;
baseLaunchCost = ItemStack.with();
startingItems = ItemStack.list(Items.copper, 140, Items.lead, 50);
startingItems = list(copper, 140, lead, 50);
conditionWave = 20;
launchPeriod = 20;
zoneRequirements = ZoneRequirement.with(desertWastes, 20, craters, 15);
blockRequirements = new Block[]{Blocks.graphitePress, Blocks.combustionGenerator, Blocks.kiln, Blocks.mechanicalPump};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand};
resources = with(copper, scrap, lead, coal, sand);
requirements = with(
new ZoneWave(desertWastes, 20),
new ZoneWave(craters, 15),
new Unlock(Blocks.graphitePress),
new Unlock(Blocks.combustionGenerator),
new Unlock(Blocks.kiln),
new Unlock(Blocks.mechanicalPump)
);
}};
stainedMountains = new Zone("stainedMountains", new MapGenerator("stainedMountains", 2)
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
loadout = Loadouts.basicFoundation;
startingItems = ItemStack.list(Items.copper, 200, Items.lead, 50);
startingItems = list(copper, 200, lead, 50);
conditionWave = 10;
launchPeriod = 10;
zoneRequirements = ZoneRequirement.with(frozenForest, 15);
blockRequirements = new Block[]{Blocks.pneumaticDrill, Blocks.powerNode, Blocks.turbineGenerator};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.sand};
resources = with(copper, scrap, lead, coal, titanium, sand);
requirements = with(
new ZoneWave(frozenForest, 15),
new Unlock(Blocks.pneumaticDrill),
new Unlock(Blocks.powerNode),
new Unlock(Blocks.turbineGenerator)
);
}};
fungalPass = new Zone("fungalPass", new MapGenerator("fungalPass")){{
startingItems = ItemStack.list(Items.copper, 250, Items.lead, 250, Items.metaglass, 100, Items.graphite, 100);
zoneRequirements = ZoneRequirement.with(stainedMountains, 15);
blockRequirements = new Block[]{Blocks.daggerFactory, Blocks.crawlerFactory, Blocks.door, Blocks.siliconSmelter};
resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.titanium, Items.sand};
startingItems = list(copper, 250, lead, 250, Items.metaglass, 100, Items.graphite, 100);
resources = with(copper, lead, coal, titanium, sand);
configureObjective = new Launched(this);
requirements = with(
new ZoneWave(stainedMountains, 15),
new Unlock(Blocks.daggerFactory),
new Unlock(Blocks.crawlerFactory),
new Unlock(Blocks.door),
new Unlock(Blocks.siliconSmelter)
);
}};
overgrowth = new Zone("overgrowth", new MapGenerator("overgrowth")){{
startingItems = ItemStack.list(Items.copper, 1500, Items.lead, 1000, Items.silicon, 500, Items.metaglass, 250);
startingItems = list(copper, 1500, lead, 1000, Items.silicon, 500, Items.metaglass, 250);
conditionWave = 12;
launchPeriod = 4;
loadout = Loadouts.basicNucleus;
zoneRequirements = ZoneRequirement.with(craters, 40, fungalPass, 10);
blockRequirements = new Block[]{Blocks.cultivator, Blocks.sporePress, Blocks.titanFactory, Blocks.wraithFactory};
resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.titanium, Items.sand, Items.thorium, Items.scrap};
configureObjective = new Launched(this);
resources = with(copper, lead, coal, titanium, sand, thorium, scrap);
requirements = with(
new ZoneWave(craters, 40),
new Launched(fungalPass),
new Unlock(Blocks.cultivator),
new Unlock(Blocks.sporePress),
new Unlock(Blocks.titanFactory),
new Unlock(Blocks.wraithFactory)
);
}};
tarFields = new Zone("tarFields", new MapGenerator("tarFields")
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
loadout = Loadouts.basicFoundation;
startingItems = ItemStack.list(Items.copper, 250, Items.lead, 100);
startingItems = list(copper, 250, lead, 100);
conditionWave = 15;
launchPeriod = 10;
zoneRequirements = ZoneRequirement.with(ruinousShores, 20);
blockRequirements = new Block[]{Blocks.coalCentrifuge, Blocks.conduit, Blocks.wave};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium, Items.sand};
requirements = with(new ZoneWave(ruinousShores, 20));
resources = with(copper, scrap, lead, coal, titanium, thorium, sand);
requirements = with(
new ZoneWave(ruinousShores, 20),
new Unlock(Blocks.coalCentrifuge),
new Unlock(Blocks.conduit),
new Unlock(Blocks.wave)
);
}};
desolateRift = new Zone("desolateRift", new MapGenerator("desolateRift")){{
loadout = Loadouts.basicNucleus;
baseLaunchCost = ItemStack.with();
startingItems = ItemStack.list(Items.copper, 1000, Items.lead, 1000, Items.graphite, 250, Items.titanium, 250, Items.silicon, 250);
startingItems = list(copper, 1000, lead, 1000, Items.graphite, 250, titanium, 250, Items.silicon, 250);
conditionWave = 3;
launchPeriod = 2;
zoneRequirements = ZoneRequirement.with(tarFields, 20);
blockRequirements = new Block[]{Blocks.thermalGenerator, Blocks.thoriumReactor};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.sand, Items.thorium};
resources = with(copper, scrap, lead, coal, titanium, sand, thorium);
requirements = with(
new ZoneWave(tarFields, 20),
new Unlock(Blocks.thermalGenerator),
new Unlock(Blocks.thoriumReactor)
);
}};
/*
@@ -174,21 +219,23 @@ public class Zones implements ContentList{
startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500);
conditionWave = 3;
launchPeriod = 2;
zoneRequirements = ZoneRequirement.with(stainedMountains, 40);
requirements = with(stainedMountains, 40);
blockRequirements = new Block[]{Blocks.thermalGenerator};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand};
resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand};
}};*/
nuclearComplex = new Zone("nuclearComplex", new MapGenerator("nuclearProductionComplex", 1)
.decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{
loadout = Loadouts.basicNucleus;
baseLaunchCost = ItemStack.with();
startingItems = ItemStack.list(Items.copper, 1250, Items.lead, 1500, Items.silicon, 400, Items.metaglass, 250);
startingItems = list(copper, 1250, lead, 1500, Items.silicon, 400, Items.metaglass, 250);
conditionWave = 30;
launchPeriod = 15;
zoneRequirements = ZoneRequirement.with(fungalPass, 8);
blockRequirements = new Block[]{Blocks.thermalGenerator, Blocks.laserDrill};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium, Items.sand};
resources = with(copper, scrap, lead, coal, titanium, thorium, sand);
requirements = with(
new Launched(fungalPass),
new Unlock(Blocks.thermalGenerator),
new Unlock(Blocks.laserDrill)
);
}};
/*
@@ -198,9 +245,9 @@ public class Zones implements ContentList{
startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500);
conditionWave = 3;
launchPeriod = 2;
zoneRequirements = ZoneRequirement.with(nuclearComplex, 40);
requirements = with(nuclearComplex, 40);
blockRequirements = new Block[]{Blocks.thermalGenerator};
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium};
resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium};
}};*/
}
}

View File

@@ -1,12 +1,13 @@
package io.anuke.mindustry.core;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@@ -23,7 +24,7 @@ public class ContentLoader{
private ObjectMap<String, MappableContent>[] contentNameMap = new ObjectMap[ContentType.values().length];
private Array<Content>[] contentMap = new Array[ContentType.values().length];
private MappableContent[][] temporaryMapper;
private ObjectSet<Consumer<Content>> initialization = new ObjectSet<>();
private ObjectSet<Cons<Content>> initialization = new ObjectSet<>();
private ContentList[] content = {
new Fx(),
new Items(),
@@ -104,13 +105,20 @@ public class ContentLoader{
}
/** Initializes all content with the specified function. */
private void initialize(Consumer<Content> callable){
private void initialize(Cons<Content> callable){
if(initialization.contains(callable)) return;
for(ContentType type : ContentType.values()){
for(Content content : contentMap[type.ordinal()]){
//TODO catch error and display it per mod
callable.accept(content);
try{
callable.get(content);
}catch(Throwable e){
if(content.mod != null){
mods.handleError(new ModLoadException(content, e), content.mod);
}else{
throw new RuntimeException(e);
}
}
}
}

View File

@@ -2,6 +2,8 @@ package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.audio.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*;
@@ -27,8 +29,8 @@ import java.text.*;
import java.util.*;
import static io.anuke.arc.Core.*;
import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.Vars.net;
import static io.anuke.mindustry.Vars.*;
/**
* Control module.
@@ -54,6 +56,9 @@ public class Control implements ApplicationListener, Loadable{
Events.on(StateChangeEvent.class, event -> {
if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){
Time.runTask(5f, platform::updateRPC);
for(Sound sound : assets.getAll(Sound.class, new Array<>())){
sound.stop();
}
}
});
@@ -146,11 +151,15 @@ public class Control implements ApplicationListener, Loadable{
});
Events.on(ZoneRequireCompleteEvent.class, e -> {
ui.hudfrag.showToast(Core.bundle.format("zone.requirement.complete", state.wave, e.zone.localizedName));
if(e.objective.display() != null){
ui.hudfrag.showToast(Core.bundle.format("zone.requirement.complete", e.zoneForMet.localizedName, e.objective.display()));
}
});
Events.on(ZoneConfigureCompleteEvent.class, e -> {
ui.hudfrag.showToast(Core.bundle.format("zone.config.complete", e.zone.configureWave));
if(e.zone.configureObjective.display() != null){
ui.hudfrag.showToast(Core.bundle.format("zone.config.unlocked", e.zone.configureObjective.display()));
}
});
Events.on(Trigger.newGame, () -> {
@@ -166,6 +175,12 @@ public class Control implements ApplicationListener, Loadable{
Effects.shake(5f, 5f, core);
});
});
Events.on(UnitDestroyEvent.class, e -> {
if(e.unit instanceof BaseUnit && world.isZone()){
data.unlockContent(((BaseUnit)e.unit).getType());
}
});
}
@Override
@@ -239,7 +254,7 @@ public class Control implements ApplicationListener, Loadable{
logic.reset();
net.reset();
world.loadGenerator(zone.generator);
zone.rules.accept(state.rules);
zone.rules.get(state.rules);
state.rules.zone = zone;
for(Tile core : state.teams.get(defaultTeam).cores){
for(ItemStack stack : zone.getStartingItems()){
@@ -287,7 +302,7 @@ public class Control implements ApplicationListener, Loadable{
world.endMapLoad();
zone.rules.accept(state.rules);
zone.rules.get(state.rules);
state.rules.zone = zone;
for(Tile core : state.teams.get(defaultTeam).cores){
for(ItemStack stack : zone.getStartingItems()){
@@ -401,6 +416,7 @@ public class Control implements ApplicationListener, Loadable{
music.update();
loops.update();
Time.updateGlobal();
if(Core.input.keyTap(Binding.fullscreen)){
boolean full = settings.getBool("fullscreen");

View File

@@ -2,10 +2,10 @@ package io.anuke.mindustry.core;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.ObjectSet.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
@@ -18,6 +18,8 @@ import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.BuildBlock.*;
import io.anuke.mindustry.world.blocks.power.*;
import java.util.*;
import static io.anuke.mindustry.Vars.*;
/**
@@ -79,13 +81,12 @@ public class Logic implements ApplicationListener{
Events.on(BlockBuildEndEvent.class, event -> {
if(!event.breaking){
TeamData data = state.teams.get(event.team);
//painful O(n) iteration + copy
for(int i = 0; i < data.brokenBlocks.size; i++){
BrokenBlock b = data.brokenBlocks.get(i);
if(b.x == event.tile.x && b.y == event.tile.y){
data.brokenBlocks.removeIndex(i);
break;
Iterator<BrokenBlock> it = data.brokenBlocks.iterator();
while(it.hasNext()){
BrokenBlock b = it.next();
Block block = content.block(b.block);
if(event.tile.block().bounds(event.tile.x, event.tile.y, Tmp.r1).overlaps(block.bounds(b.x, b.y, Tmp.r2))){
it.remove();
}
}
}
@@ -136,8 +137,7 @@ public class Logic implements ApplicationListener{
public void runWave(){
spawner.spawnEnemies();
state.wave++;
state.wavetime = world.isZone() && world.getZone().isBossWave(state.wave) ? state.rules.waveSpacing * state.rules.bossWaveMultiplier :
world.isZone() && world.getZone().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
state.wavetime = world.isZone() && world.getZone().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
Events.fire(new WaveEvent());
}
@@ -176,12 +176,16 @@ public class Logic implements ApplicationListener{
ui.hudfrag.showLaunch();
}
for(Tile tile : new ObjectSetIterator<>(state.teams.get(defaultTeam).cores)){
for(Tile tile : state.teams.get(defaultTeam).cores){
Effects.effect(Fx.launch, tile);
}
if(world.getZone() != null){
world.getZone().setLaunched();
}
Time.runTask(30f, () -> {
for(Tile tile : new ObjectSetIterator<>(state.teams.get(defaultTeam).cores)){
for(Tile tile : state.teams.get(defaultTeam).cores){
for(Item item : content.items()){
if(tile == null || tile.entity == null || tile.entity.items == null) continue;
data.addItem(item, tile.entity.items.get(item));

View File

@@ -15,14 +15,15 @@ import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.modules.*;
@@ -198,6 +199,15 @@ public class NetClient implements ApplicationListener{
return "[#" + player.color.toString().toUpperCase() + "]" + name;
}
@Remote(called = Loc.client, variants = Variant.one)
public static void onConnect(String ip, int port){
netClient.disconnectQuietly();
state.set(State.menu);
logic.reset();
ui.join.connect(ip, port);
}
@Remote(targets = Loc.client)
public static void onPing(Player player, long time){
Call.onPingResponse(player.con, time);
@@ -245,6 +255,11 @@ public class NetClient implements ApplicationListener{
ui.showText("", message);
}
@Remote(variants = Variant.both)
public static void onSetRules(Rules rules){
state.rules = rules;
}
@Remote(variants = Variant.both)
public static void onWorldDataBegin(){
entities.clear();
@@ -456,7 +471,7 @@ public class NetClient implements ApplicationListener{
player.pointerX, player.pointerY, player.rotation, player.baseRotation,
player.velocity().x, player.velocity().y,
player.getMineTile(),
player.isBoosting, player.isShooting, ui.chatfrag.chatOpen(),
player.isBoosting, player.isShooting, ui.chatfrag.chatOpen(), player.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);
@@ -479,4 +494,4 @@ public class NetClient implements ApplicationListener{
return result;
}
}
}
}

View File

@@ -94,11 +94,6 @@ public class NetServer implements ApplicationListener{
return;
}
if(admins.isIDBanned(uuid)){
con.kick(KickReason.banned);
return;
}
if(admins.getPlayerLimit() > 0 && playerGroup.size() >= admins.getPlayerLimit()){
con.kick(KickReason.playerLimit);
return;
@@ -336,6 +331,8 @@ public class NetServer implements ApplicationListener{
player.sendMessage("[scarlet]Did you really expect to be able to kick an admin?");
}else if(found.isLocal){
player.sendMessage("[scarlet]Local players cannot be kicked.");
}else if(found.getTeam() != player.getTeam()){
player.sendMessage("[scarlet]Only players on your team can be kicked.");
}else{
if(!vtime.get()){
player.sendMessage("[scarlet]You must wait " + voteTime/60 + " minutes between votekicks.");
@@ -357,6 +354,11 @@ public class NetServer implements ApplicationListener{
if(currentlyKicking[0] == null){
player.sendMessage("[scarlet]Nobody is being voted on.");
}else{
if(player.isLocal){
player.sendMessage("Local players can't vote. Kick the player yourself instead.");
return;
}
//hosts can vote all they want
if(player.uuid != null && (currentlyKicking[0].voted.contains(player.uuid) || currentlyKicking[0].voted.contains(admins.getInfo(player.uuid).lastIP))){
player.sendMessage("[scarlet]You've already voted. Sit down.");
@@ -450,7 +452,7 @@ public class NetServer implements ApplicationListener{
float rotation, float baseRotation,
float xVelocity, float yVelocity,
Tile mining,
boolean boosting, boolean shooting, boolean chatting,
boolean boosting, boolean shooting, boolean chatting, boolean building,
BuildRequest[] requests,
float viewX, float viewY, float viewWidth, float viewHeight
){
@@ -477,6 +479,7 @@ public class NetServer implements ApplicationListener{
player.isTyping = chatting;
player.isBoosting = boosting;
player.isShooting = shooting;
player.isBuilding = building;
player.buildQueue().clear();
for(BuildRequest req : requests){
if(req == null) continue;

View File

@@ -4,13 +4,13 @@ import io.anuke.arc.*;
import io.anuke.arc.Input.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.mobile;
@@ -24,23 +24,19 @@ public interface Platform{
default void inviteFriends(){}
/** Steam: Share a map on the workshop.*/
default void publishMap(Map map){}
default void publish(Publishable pub){}
/** Steam: View a listing on the workshop.*/
default void viewListing(Publishable pub){}
/** Steam: View a listing on the workshop by an ID.*/
default void viewListingID(String mapid){}
/** Steam: Return external workshop maps to be loaded.*/
default Array<FileHandle> getExternalMaps(){
return Array.with();
default Array<FileHandle> getWorkshopContent(Class<? extends Publishable> type){
return new Array<>(0);
}
/** Steam: View a map listing on the workshop.*/
default void viewMapListing(Map map){}
/** Steam: View a map listing on the workshop.*/
default void viewMapListing(String mapid){}
/** Steam: View map workshop info, removing the map ID tag if its listing is deleted.
* Also presents the option to update the map. */
default void viewMapListingInfo(Map map){}
/** Steam: Open workshop for maps.*/
default void openWorkshop(){}
@@ -76,11 +72,6 @@ public interface Platform{
default void updateRPC(){
}
/** Whether donating is supported. */
default boolean canDonate(){
return false;
}
/** Must be a base64 string 8 bytes in length. */
default String getUUID(){
String uuid = Core.settings.getString("uuid", "");
@@ -105,12 +96,12 @@ public interface Platform{
* @param open Whether to open or save files
* @param extension File extension to filter
*/
default void showFileChooser(boolean open, String extension, Consumer<FileHandle> cons){
default void showFileChooser(boolean open, String extension, Cons<FileHandle> cons){
new FileChooser(open ? "$open" : "$save", file -> file.extension().toLowerCase().equals(extension), open, file -> {
if(!open){
cons.accept(file.parent().child(file.nameWithoutExtension() + "." + extension));
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{
cons.accept(file);
cons.get(file);
}
}).show();
}

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.glutils.*;
@@ -22,6 +22,7 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import static io.anuke.arc.Core.*;
@@ -239,7 +240,7 @@ public class Renderer implements ApplicationListener{
blocks.drawBlocks(Layer.block);
blocks.drawFog();
blocks.drawBroken();
blocks.drawDestroyed();
Draw.shader(Shaders.blockbuild, true);
blocks.drawBlocks(Layer.placement);
@@ -335,19 +336,19 @@ public class Renderer implements ApplicationListener{
Draw.color(0, 0, 0, 0.4f);
float rad = 1.6f;
Consumer<Unit> draw = u -> {
Cons<Unit> draw = u -> {
float size = Math.max(u.getIconRegion().getWidth(), u.getIconRegion().getHeight()) * Draw.scl;
Draw.rect("circle-shadow", u.x, u.y, size * rad, size * rad);
};
for(EntityGroup<? extends BaseUnit> group : unitGroups){
if(!group.isEmpty()){
group.draw(unit -> !unit.isDead(), draw::accept);
group.draw(unit -> !unit.isDead(), draw::get);
}
}
if(!playerGroup.isEmpty()){
playerGroup.draw(unit -> !unit.isDead(), draw::accept);
playerGroup.draw(unit -> !unit.isDead(), draw::get);
}
Draw.color();

View File

@@ -12,7 +12,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.freetype.*;
import io.anuke.arc.freetype.FreeTypeFontGenerator.*;
import io.anuke.arc.freetype.FreetypeFontLoader.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*;
@@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
public DeployDialog deploy;
public TechTreeDialog tech;
public MinimapDialog minimap;
public SchematicsDialog schematics;
public ModsDialog mods;
public Cursor drillCursor, unloadCursor;
@@ -185,6 +186,13 @@ public class UI implements ApplicationListener, Loadable{
Core.scene.act();
Core.scene.draw();
if(Core.input.keyTap(KeyCode.MOUSE_LEFT) && Core.scene.getKeyboardFocus() instanceof TextField){
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(!(e instanceof TextField)){
Core.scene.setKeyboardFocus(null);
}
}
//draw overlay for buttons
if(state.rules.tutorial){
control.tutorial.draw();
@@ -225,6 +233,7 @@ public class UI implements ApplicationListener, Loadable{
tech = new TechTreeDialog();
minimap = new MinimapDialog();
mods = new ModsDialog();
schematics = new SchematicsDialog();
Group group = Core.scene.root;
@@ -238,7 +247,6 @@ public class UI implements ApplicationListener, Loadable{
Core.scene.add(menuGroup);
Core.scene.add(hudGroup);
control.input.getFrag().build(hudGroup);
hudfrag.build(hudGroup);
menufrag.build(menuGroup);
chatfrag.container().build(hudGroup);
@@ -271,7 +279,7 @@ public class UI implements ApplicationListener, Loadable{
});
}
public void showTextInput(String titleText, String dtext, int textLength, String def, boolean inumeric, Consumer<String> confirmed){
public void showTextInput(String titleText, String dtext, int textLength, String def, boolean inumeric, Cons<String> confirmed){
if(mobile){
Core.input.getTextInput(new TextInput(){{
this.title = (titleText.startsWith("$") ? Core.bundle.get(titleText.substring(1)) : titleText);
@@ -288,7 +296,7 @@ public class UI implements ApplicationListener, Loadable{
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
buttons.defaults().size(120, 54).pad(4);
buttons.addButton("$ok", () -> {
confirmed.accept(field.getText());
confirmed.get(field.getText());
hide();
}).disabled(b -> field.getText().isEmpty());
buttons.addButton("$cancel", this::hide);
@@ -296,11 +304,11 @@ public class UI implements ApplicationListener, Loadable{
}
}
public void showTextInput(String title, String text, String def, Consumer<String> confirmed){
showTextInput(title, text, 24, def, confirmed);
public void showTextInput(String title, String text, String def, Cons<String> confirmed){
showTextInput(title, text, 32, def, confirmed);
}
public void showTextInput(String titleText, String text, int textLength, String def, Consumer<String> confirmed){
public void showTextInput(String titleText, String text, int textLength, String def, Cons<String> confirmed){
showTextInput(titleText, text, textLength, def, false, confirmed);
}
@@ -308,7 +316,7 @@ public class UI implements ApplicationListener, Loadable{
Table table = new Table();
table.setFillParent(true);
table.actions(Actions.fadeOut(7f, Interpolation.fade), Actions.remove());
table.top().add(info).padTop(10);
table.top().add(info).style(Styles.outlineLabel).padTop(10);
Core.scene.add(table);
}
@@ -339,6 +347,7 @@ public class UI implements ApplicationListener, Loadable{
}
public void showException(String text, Throwable exc){
loadfrag.hide();
new Dialog(""){{
String message = Strings.getFinalMesage(exc);
@@ -395,9 +404,9 @@ public class UI implements ApplicationListener, Loadable{
showConfirm(title, text, null, confirmed);
}
public void showConfirm(String title, String text, BooleanProvider hide, Runnable confirmed){
public void showConfirm(String title, String text, Boolp hide, Runnable confirmed){
FloatingDialog dialog = new FloatingDialog(title);
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.addButton("$cancel", dialog::hide);
@@ -420,7 +429,7 @@ public class UI implements ApplicationListener, Loadable{
public void showCustomConfirm(String title, String text, String yes, String no, Runnable confirmed){
FloatingDialog dialog = new FloatingDialog(title);
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.addButton(no, dialog::hide);

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.game;
package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.Files.*;

View File

@@ -185,6 +185,10 @@ public class World{
Events.fire(new WorldLoadEvent());
}
public void setGenerating(boolean gen){
this.generating = gen;
}
public boolean isGenerating(){
return generating;
}
@@ -272,6 +276,7 @@ public class World{
}
public void removeBlock(Tile tile){
if(tile == null) return;
tile.link().getLinkedTiles(other -> other.setBlock(Blocks.air));
}

View File

@@ -1,16 +1,19 @@
package io.anuke.mindustry.game;
package io.anuke.mindustry.ctype;
import io.anuke.arc.files.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.type.*;
/** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */
public abstract class Content{
public abstract class Content implements Comparable<Content>{
public final short id;
/** The mod that loaded this piece of content. */
public @Nullable LoadedMod mod;
/** File that this content was loaded from. */
public @Nullable FileHandle sourceFile;
public Content(){
this.id = (short)Vars.content.getBy(getContentType()).size;
@@ -34,6 +37,11 @@ public abstract class Content{
public void load(){
}
@Override
public int compareTo(Content c){
return Integer.compare(id, c.id);
}
@Override
public String toString(){
return getContentType().name() + "#" + id;

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.game;
package io.anuke.mindustry.ctype;
/** Interface for a list of content to be loaded in {@link io.anuke.mindustry.core.ContentLoader}. */
public interface ContentList{

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.game;
package io.anuke.mindustry.ctype;
import io.anuke.mindustry.*;

View File

@@ -1,10 +1,11 @@
package io.anuke.mindustry.game;
package io.anuke.mindustry.ctype;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.ui.Cicon;
/** Base interface for an unlockable content type. */
public abstract class UnlockableContent extends MappableContent{
@@ -13,7 +14,7 @@ public abstract class UnlockableContent extends MappableContent{
/** Localized description. May be null. */
public String description;
/** Icons by Cicon ID.*/
protected TextureRegion[] cicons = new TextureRegion[Cicon.all.length];
protected TextureRegion[] cicons = new TextureRegion[io.anuke.mindustry.ui.Cicon.all.length];
public UnlockableContent(String name){
super(name);
@@ -31,7 +32,11 @@ public abstract class UnlockableContent extends MappableContent{
/** Returns a specific content icon, or the region {contentType}-{name} if not found.*/
public TextureRegion icon(Cicon icon){
if(cicons[icon.ordinal()] == null){
cicons[icon.ordinal()] = Core.atlas.find(getContentType().name() + "-" + name + "-" + icon.name(), Core.atlas.find(getContentType().name() + "-" + name + "-full", Core.atlas.find(getContentType().name() + "-" + name, Core.atlas.find(name))));
cicons[icon.ordinal()] = Core.atlas.find(getContentType().name() + "-" + name + "-" + icon.name(),
Core.atlas.find(getContentType().name() + "-" + name + "-full",
Core.atlas.find(getContentType().name() + "-" + name,
Core.atlas.find(name,
Core.atlas.find(name + "1")))));
}
return cicons[icon.ordinal()];
}
@@ -62,6 +67,11 @@ public abstract class UnlockableContent extends MappableContent{
return Vars.data.isUnlocked(this);
}
/** @return whether this content is unlocked, or the player is in a custom game. */
public final boolean unlockedCur(){
return Vars.data.isUnlocked(this) || !Vars.world.isZone();
}
public final boolean locked(){
return !unlocked();
}

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.collection.IntArray;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Bresenham2;
import io.anuke.arc.util.Structs;
@@ -113,8 +113,8 @@ public enum EditorTool{
return;
}
Predicate<Tile> tester;
Consumer<Tile> setter;
Boolf<Tile> tester;
Cons<Tile> setter;
if(editor.drawBlock.isOverlay()){
Block dest = tile.overlay();
@@ -146,7 +146,7 @@ public enum EditorTool{
}
}
void fill(MapEditor editor, int x, int y, boolean replace, Predicate<Tile> tester, Consumer<Tile> filler){
void fill(MapEditor editor, int x, int y, boolean replace, Boolf<Tile> tester, Cons<Tile> filler){
int width = editor.width(), height = editor.height();
if(replace){
@@ -154,8 +154,8 @@ public enum EditorTool{
for(int cx = 0; cx < width; cx++){
for(int cy = 0; cy < height; cy++){
Tile tile = editor.tile(cx, cy);
if(tester.test(tile)){
filler.accept(tile);
if(tester.get(tile)){
filler.get(tile);
}
}
}
@@ -173,23 +173,23 @@ public enum EditorTool{
y = Pos.y(popped);
x1 = x;
while(x1 >= 0 && tester.test(editor.tile(x1, y))) x1--;
while(x1 >= 0 && tester.get(editor.tile(x1, y))) x1--;
x1++;
boolean spanAbove = false, spanBelow = false;
while(x1 < width && tester.test(editor.tile(x1, y))){
filler.accept(editor.tile(x1, y));
while(x1 < width && tester.get(editor.tile(x1, y))){
filler.get(editor.tile(x1, y));
if(!spanAbove && y > 0 && tester.test(editor.tile(x1, y - 1))){
if(!spanAbove && y > 0 && tester.get(editor.tile(x1, y - 1))){
stack.add(Pos.get(x1, y - 1));
spanAbove = true;
}else if(spanAbove && !tester.test(editor.tile(x1, y - 1))){
}else if(spanAbove && !tester.get(editor.tile(x1, y - 1))){
spanAbove = false;
}
if(!spanBelow && y < height - 1 && tester.test(editor.tile(x1, y + 1))){
if(!spanBelow && y < height - 1 && tester.get(editor.tile(x1, y + 1))){
stack.add(Pos.get(x1, y + 1));
spanBelow = true;
}else if(spanBelow && y < height - 1 && !tester.test(editor.tile(x1, y + 1))){
}else if(spanBelow && y < height - 1 && !tester.get(editor.tile(x1, y + 1))){
spanBelow = false;
}
x1++;

View File

@@ -2,8 +2,8 @@ package io.anuke.mindustry.editor;
import io.anuke.arc.collection.StringMap;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.func.Cons;
import io.anuke.arc.func.Boolf;
import io.anuke.arc.graphics.Pixmap;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Structs;
@@ -144,11 +144,11 @@ public class MapEditor{
drawBlocks(x, y, false, tile -> true);
}
public void drawBlocks(int x, int y, Predicate<Tile> tester){
public void drawBlocks(int x, int y, Boolf<Tile> tester){
drawBlocks(x, y, false, tester);
}
public void drawBlocks(int x, int y, boolean square, Predicate<Tile> tester){
public void drawBlocks(int x, int y, boolean square, Boolf<Tile> tester){
if(drawBlock.isMultiblock()){
x = Mathf.clamp(x, (drawBlock.size - 1) / 2, width() - drawBlock.size / 2 - 1);
y = Mathf.clamp(y, (drawBlock.size - 1) / 2, height() - drawBlock.size / 2 - 1);
@@ -180,8 +180,8 @@ public class MapEditor{
}else{
boolean isFloor = drawBlock.isFloor() && drawBlock != Blocks.air;
Consumer<Tile> drawer = tile -> {
if(!tester.test(tile)) return;
Cons<Tile> drawer = tile -> {
if(!tester.get(tile)) return;
//remove linked tiles blocking the way
if(!isFloor && (tile.isLinked() || tile.block().isMultiblock())){
@@ -209,7 +209,7 @@ public class MapEditor{
}
}
public void drawCircle(int x, int y, Consumer<Tile> drawer){
public void drawCircle(int x, int y, Cons<Tile> drawer){
for(int rx = -brushSize; rx <= brushSize; rx++){
for(int ry = -brushSize; ry <= brushSize; ry++){
if(Mathf.dst2(rx, ry) <= (brushSize - 0.5f) * (brushSize - 0.5f)){
@@ -219,13 +219,13 @@ public class MapEditor{
continue;
}
drawer.accept(tile(wx, wy));
drawer.get(tile(wx, wy));
}
}
}
}
public void drawSquare(int x, int y, Consumer<Tile> drawer){
public void drawSquare(int x, int y, Cons<Tile> drawer){
for(int rx = -brushSize; rx <= brushSize; rx++){
for(int ry = -brushSize; ry <= brushSize; ry++){
int wx = x + rx, wy = y + ry;
@@ -234,7 +234,7 @@ public class MapEditor{
continue;
}
drawer.accept(tile(wx, wy));
drawer.get(tile(wx, wy));
}
}
}

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.editor;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*;
@@ -25,6 +25,7 @@ import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
@@ -149,15 +150,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(steam){
menu.cont.addImageTextButton("$editor.publish.workshop", Icon.linkSmall, () -> {
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){
platform.viewMapListing(editor.getTags().get("steamid"));
platform.viewListingID(editor.getTags().get("steamid"));
return;
}
Map map = save();
if(editor.getTags().containsKey("steamid") && map != null){
platform.viewMapListingInfo(map);
platform.viewListing(map);
return;
}
@@ -173,7 +175,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
return;
}
platform.publishMap(map);
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
menu.cont.row();
@@ -423,7 +425,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
ButtonGroup<ImageButton> group = new ButtonGroup<>();
Table[] lastTable = {null};
Consumer<EditorTool> addTool = tool -> {
Cons<EditorTool> addTool = tool -> {
ImageButton button = new ImageButton(Core.atlas.drawable("icon-" + tool.name() + "-small"), Styles.clearTogglei);
button.clicked(() -> {
@@ -505,14 +507,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
ImageButton grid = tools.addImageButton(Icon.gridSmall, Styles.clearTogglei, () -> view.setGrid(!view.isGrid())).get();
addTool.accept(EditorTool.zoom);
addTool.get(EditorTool.zoom);
tools.row();
ImageButton undo = tools.addImageButton(Icon.undoSmall, Styles.cleari, editor::undo).get();
ImageButton redo = tools.addImageButton(Icon.redoSmall, Styles.cleari, editor::redo).get();
addTool.accept(EditorTool.pick);
addTool.get(EditorTool.pick);
tools.row();
@@ -523,14 +525,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.gray : Color.white));
grid.update(() -> grid.setChecked(view.isGrid()));
addTool.accept(EditorTool.line);
addTool.accept(EditorTool.pencil);
addTool.accept(EditorTool.eraser);
addTool.get(EditorTool.line);
addTool.get(EditorTool.pencil);
addTool.get(EditorTool.eraser);
tools.row();
addTool.accept(EditorTool.fill);
addTool.accept(EditorTool.spray);
addTool.get(EditorTool.fill);
addTool.get(EditorTool.spray);
ImageButton rotate = tools.addImageButton(Icon.arrow16Small, Styles.cleari, () -> editor.rotation = (editor.rotation + 1) % 4).get();
rotate.getImage().update(() -> {

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.editor;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Pixmap.*;
import io.anuke.arc.math.*;
@@ -27,7 +27,7 @@ import static io.anuke.mindustry.Vars.*;
@SuppressWarnings("unchecked")
public class MapGenerateDialog extends FloatingDialog{
private final Supplier<GenerateFilter>[] filterTypes = new Supplier[]{
private final Prov<GenerateFilter>[] filterTypes = new Prov[]{
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
BlendFilter::new, MirrorFilter::new, ClearFilter::new
@@ -48,7 +48,7 @@ public class MapGenerateDialog extends FloatingDialog{
private GenTile returnTile = new GenTile();
private GenTile[][] buffer1, buffer2;
private Consumer<Array<GenerateFilter>> applier;
private Cons<Array<GenerateFilter>> applier;
private CachedTile ctile = new CachedTile(){
//nothing.
@Override
@@ -95,13 +95,13 @@ public class MapGenerateDialog extends FloatingDialog{
onResize(this::rebuildFilters);
}
public void show(Array<GenerateFilter> filters, Consumer<Array<GenerateFilter>> applier){
public void show(Array<GenerateFilter> filters, Cons<Array<GenerateFilter>> applier){
this.filters = filters;
this.applier = applier;
show();
}
public void show(Consumer<Array<GenerateFilter>> applier){
public void show(Cons<Array<GenerateFilter>> applier){
show(this.filters, applier);
}
@@ -289,7 +289,7 @@ public class MapGenerateDialog extends FloatingDialog{
selection.setFillParent(false);
selection.cont.defaults().size(210f, 60f);
int i = 0;
for(Supplier<GenerateFilter> gen : filterTypes){
for(Prov<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
if(!applied && filter.buffered) continue;
@@ -334,7 +334,7 @@ public class MapGenerateDialog extends FloatingDialog{
texture = null;
}
applier.accept(filters);
applier.get(filters);
}
void update(){

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
@@ -13,7 +13,7 @@ import static io.anuke.mindustry.Vars.maps;
public class MapLoadDialog extends FloatingDialog{
private Map selected = null;
public MapLoadDialog(Consumer<Map> loader){
public MapLoadDialog(Cons<Map> loader){
super("$editor.loadmap");
shown(this::rebuild);
@@ -22,7 +22,7 @@ public class MapLoadDialog extends FloatingDialog{
button.setDisabled(() -> selected == null);
button.clicked(() -> {
if(selected != null){
loader.accept(selected);
loader.get(selected);
hide();
}
});

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.Core;
import io.anuke.arc.*;
import io.anuke.arc.collection.IntSet;
import io.anuke.arc.collection.IntSet.IntSetIterator;
import io.anuke.arc.graphics.Color;
@@ -10,6 +10,7 @@ import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.graphics.IndexedRenderer;
import io.anuke.mindustry.world.Block;
@@ -29,7 +30,11 @@ public class MapRenderer implements Disposable{
public MapRenderer(MapEditor editor){
this.editor = editor;
texture = Core.atlas.find("clear-editor").getTexture();
this.texture = Core.atlas.find("clear-editor").getTexture();
Events.on(ContentReloadEvent.class, e -> {
texture = Core.atlas.find("clear-editor").getTexture();
});
}
public void resize(int width, int height){

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.gen.*;
@@ -10,7 +10,7 @@ public class MapResizeDialog extends FloatingDialog{
private static final int minSize = 50, maxSize = 500, increment = 50;
int width, height;
public MapResizeDialog(MapEditor editor, IntPositionConsumer cons){
public MapResizeDialog(MapEditor editor, Intc2 cons){
super("$editor.resizemap");
shown(() -> {
cont.clear();
@@ -46,8 +46,8 @@ public class MapResizeDialog extends FloatingDialog{
buttons.defaults().size(200f, 50f);
buttons.addButton("$cancel", this::hide);
buttons.addButton("$editor.resize", () -> {
cons.accept(width, height);
buttons.addButton("$ok", () -> {
cons.get(width, height);
hide();
});
}

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.maps.*;
@@ -10,9 +10,9 @@ import static io.anuke.mindustry.Vars.ui;
public class MapSaveDialog extends FloatingDialog{
private TextField field;
private Consumer<String> listener;
private Cons<String> listener;
public MapSaveDialog(Consumer<String> cons){
public MapSaveDialog(Cons<String> cons){
super("$editor.savemap");
field = new TextField();
listener = cons;
@@ -43,7 +43,7 @@ public class MapSaveDialog extends FloatingDialog{
TextButton button = new TextButton("$save");
button.clicked(() -> {
if(!invalid()){
cons.accept(field.getText());
cons.get(field.getText());
hide();
}
});
@@ -53,7 +53,7 @@ public class MapSaveDialog extends FloatingDialog{
public void save(){
if(!invalid()){
listener.accept(field.getText());
listener.get(field.getText());
}else{
ui.showErrorMessage("$editor.failoverwrite");
}

View File

@@ -17,6 +17,7 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.*;
@@ -140,7 +141,7 @@ public class WaveInfoDialog extends FloatingDialog{
t.margin(0).defaults().pad(3).padLeft(5f).growX().left();
t.addButton(b -> {
b.left();
b.addImage(group.type.icon(Cicon.medium)).size(32f).padRight(3);
b.addImage(group.type.icon(io.anuke.mindustry.ui.Cicon.medium)).size(32f).padRight(3);
b.add(group.type.localizedName).color(Pal.accent);
}, () -> showUpdate(group)).pad(-6f).padBottom(0f);
@@ -221,7 +222,7 @@ public class WaveInfoDialog extends FloatingDialog{
for(UnitType type : content.units()){
dialog.cont.addButton(t -> {
t.left();
t.addImage(type.icon(Cicon.medium)).size(40f).padRight(2f);
t.addImage(type.icon(io.anuke.mindustry.ui.Cicon.medium)).size(40f).padRight(2f);
t.add(type.localizedName);
}, () -> {
lastType = type;

View File

@@ -1,27 +1,22 @@
package io.anuke.mindustry.entities;
import io.anuke.annotations.Annotations.Struct;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.GridBits;
import io.anuke.arc.collection.IntQueue;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.collection.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.Bullets;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.effect.Fire;
import io.anuke.mindustry.entities.effect.Lightning;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.gen.PropCell;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.*;
@@ -32,6 +27,7 @@ public class Damage{
private static Vector2 tr = new Vector2();
private static GridBits bits = new GridBits(30, 30);
private static IntQueue propagation = new IntQueue();
private static IntSet collidedBlocks = new IntSet();
/** 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){
@@ -88,20 +84,22 @@ public class Damage{
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
collidedBlocks.clear();
tr.trns(angle, length);
IntPositionConsumer collider = (cx, cy) -> {
Intc2 collider = (cx, cy) -> {
Tile tile = world.ltile(cx, cy);
if(tile != null && tile.entity != null && tile.getTeamID() != team.ordinal() && tile.entity.collide(hitter)){
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.entity != null && tile.getTeamID() != team.ordinal() && tile.entity.collide(hitter)){
tile.entity.collision(hitter);
collidedBlocks.add(tile.pos());
hitter.getBulletType().hit(hitter, tile.worldx(), tile.worldy());
}
};
world.raycastEachWorld(x, y, x + tr.x, y + tr.y, (cx, cy) -> {
collider.accept(cx, cy);
collider.get(cx, cy);
if(large){
for(Point2 p : Geometry.d4){
collider.accept(cx + p.x, cy + p.y);
collider.get(cx + p.x, cy + p.y);
}
}
return false;
@@ -127,7 +125,7 @@ public class Damage{
rect.width += expand * 2;
rect.height += expand * 2;
Consumer<Unit> cons = e -> {
Cons<Unit> cons = e -> {
e.hitbox(hitrect);
Rectangle other = hitrect;
other.y -= expand;
@@ -148,16 +146,16 @@ public class Damage{
}
/** Damages all entities and blocks in a radius that are enemies of the team. */
public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate<Unit> predicate, Consumer<Unit> acceptor){
Consumer<Unit> cons = entity -> {
if(!predicate.test(entity)) return;
public static void damageUnits(Team team, float x, float y, float size, float damage, Boolf<Unit> predicate, Cons<Unit> acceptor){
Cons<Unit> cons = entity -> {
if(!predicate.get(entity)) return;
entity.hitbox(hitrect);
if(!hitrect.overlaps(rect)){
return;
}
entity.damage(damage);
acceptor.accept(entity);
acceptor.get(entity);
};
rect.setSize(size * 2).setCenter(x, y);
@@ -180,7 +178,7 @@ public class Damage{
/** Damages all entities and blocks in a radius that are enemies of the team. */
public static void damage(Team team, float x, float y, float radius, float damage, boolean complete){
Consumer<Unit> cons = entity -> {
Cons<Unit> cons = entity -> {
if(entity.getTeam() == team || entity.dst(x, y) > radius){
return;
}

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.entities;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.func.Cons;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Position;
@@ -142,11 +142,11 @@ public class Effects{
this.data = data;
}
public void scaled(float lifetime, Consumer<EffectContainer> cons){
public void scaled(float lifetime, Cons<EffectContainer> cons){
if(innerContainer == null) innerContainer = new EffectContainer();
if(time <= lifetime){
innerContainer.set(id, color, time, lifetime, rotation, x, y, data);
cons.accept(innerContainer);
cons.get(innerContainer);
}
}

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.entities;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.math.geom.*;
import io.anuke.mindustry.entities.traits.*;
@@ -22,8 +22,8 @@ public class EntityGroup<T extends Entity>{
private final Rectangle intersectRect = new Rectangle();
private IntMap<T> map;
private QuadTree tree;
private Consumer<T> removeListener;
private Consumer<T> addListener;
private Cons<T> removeListener;
private Cons<T> addListener;
private final Rectangle viewport = new Rectangle();
private int count = 0;
@@ -60,20 +60,20 @@ public class EntityGroup<T extends Entity>{
draw(e -> true);
}
public void draw(Predicate<T> toDraw){
public void draw(Boolf<T> toDraw){
draw(toDraw, t -> ((DrawTrait)t).draw());
}
public void draw(Predicate<T> toDraw, Consumer<T> cons){
public void draw(Boolf<T> toDraw, Cons<T> cons){
Camera cam = Core.camera;
viewport.set(cam.position.x - cam.width / 2, cam.position.y - cam.height / 2, cam.width, cam.height);
for(Entity e : all()){
if(!(e instanceof DrawTrait) || !toDraw.test((T)e) || !e.isAdded()) continue;
if(!(e instanceof DrawTrait) || !toDraw.get((T)e) || !e.isAdded()) continue;
DrawTrait draw = (DrawTrait)e;
if(viewport.overlaps(draw.getX() - draw.drawSize()/2f, draw.getY() - draw.drawSize()/2f, draw.drawSize(), draw.drawSize())){
cons.accept((T)e);
cons.get((T)e);
}
}
}
@@ -82,11 +82,11 @@ public class EntityGroup<T extends Entity>{
return useTree;
}
public void setRemoveListener(Consumer<T> removeListener){
public void setRemoveListener(Cons<T> removeListener){
this.removeListener = removeListener;
}
public void setAddListener(Consumer<T> addListener){
public void setAddListener(Cons<T> addListener){
this.addListener = addListener;
}
@@ -148,7 +148,7 @@ public class EntityGroup<T extends Entity>{
if(check.getID() == id){ //if it is indeed queued, remove it
entitiesToAdd.removeValue(check, true);
if(removeListener != null){
removeListener.accept(check);
removeListener.get(check);
}
break;
}
@@ -157,7 +157,7 @@ public class EntityGroup<T extends Entity>{
}
@SuppressWarnings("unchecked")
public void intersect(float x, float y, float width, float height, Consumer<? super T> out){
public void intersect(float x, float y, float width, float height, Cons<? super T> out){
//don't waste time for empty groups
if(isEmpty()) return;
tree().getIntersect(out, x, y, width, height);
@@ -192,10 +192,10 @@ public class EntityGroup<T extends Entity>{
return entityArray.size;
}
public int count(Predicate<T> pred){
public int count(Boolf<T> pred){
int count = 0;
for(int i = 0; i < entityArray.size; i++){
if(pred.test(entityArray.get(i))) count++;
if(pred.get(entityArray.get(i))) count++;
}
return count;
}
@@ -211,7 +211,7 @@ public class EntityGroup<T extends Entity>{
}
if(addListener != null){
addListener.accept(type);
addListener.get(type);
}
}
@@ -221,7 +221,7 @@ public class EntityGroup<T extends Entity>{
entitiesToRemove.add(type);
if(removeListener != null){
removeListener.accept(type);
removeListener.get(type);
}
}
@@ -244,10 +244,10 @@ public class EntityGroup<T extends Entity>{
map.clear();
}
public T find(Predicate<T> pred){
public T find(Boolf<T> pred){
for(int i = 0; i < entityArray.size; i++){
if(pred.test(entityArray.get(i))) return entityArray.get(i);
if(pred.get(entityArray.get(i))) return entityArray.get(i);
}
return null;

View File

@@ -0,0 +1,6 @@
package io.anuke.mindustry.entities;
public enum TargetPriority{
base,
turret
}

View File

@@ -1,8 +1,8 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.collection.EnumSet;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.func.Cons;
import io.anuke.arc.func.Boolf;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Rectangle;
@@ -78,16 +78,16 @@ public class Units{
}
/** Returns the neareset ally tile in a range. */
public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
public static TileEntity findAllyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
return indexer.findTile(team, x, y, range, pred);
}
/** Returns the neareset enemy tile in a range. */
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
if(team == Team.derelict) return null;
for(Team enemy : state.teams.enemiesOf(team)){
TileEntity entity = indexer.findTile(enemy, x, y, range, pred);
TileEntity entity = indexer.findTile(enemy, x, y, range, pred, true);
if(entity != null){
return entity;
}
@@ -101,12 +101,12 @@ public class Units{
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range, Predicate<Unit> unitPred){
public static TargetTrait closestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred){
return closestTarget(team, x, y, range, unitPred, t -> true);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range, Predicate<Unit> unitPred, Predicate<Tile> tilePred){
public static TargetTrait closestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Tile> tilePred){
if(team == Team.derelict) return null;
Unit unit = closestEnemy(team, x, y, range, unitPred);
@@ -118,14 +118,14 @@ public class Units{
}
/** Returns the closest enemy of this team. Filter by predicate. */
public static Unit closestEnemy(Team team, float x, float y, float range, Predicate<Unit> predicate){
public static Unit closestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate){
if(team == Team.derelict) return null;
result = null;
cdist = 0f;
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.isDead() || !predicate.test(e)) return;
if(e.isDead() || !predicate.get(e)) return;
float dst2 = Mathf.dst2(e.x, e.y, x, y);
if(dst2 < range*range && (result == null || dst2 < cdist)){
@@ -138,12 +138,12 @@ public class Units{
}
/** Returns the closest ally of this team. Filter by predicate. */
public static Unit closest(Team team, float x, float y, float range, Predicate<Unit> predicate){
public static Unit closest(Team team, float x, float y, float range, Boolf<Unit> predicate){
result = null;
cdist = 0f;
nearby(team, x, y, range, e -> {
if(!predicate.test(e)) return;
if(!predicate.get(e)) return;
float dist = Mathf.dst2(e.x, e.y, x, y);
if(result == null || dist < cdist){
@@ -156,32 +156,32 @@ public class Units{
}
/** Iterates over all units in a rectangle. */
public static void nearby(Team team, float x, float y, float width, float height, Consumer<Unit> cons){
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){
unitGroups[team.ordinal()].intersect(x, y, width, height, cons);
playerGroup.intersect(x, y, width, height, player -> {
if(player.getTeam() == team){
cons.accept(player);
cons.get(player);
}
});
}
/** Iterates over all units in a circle around this position. */
public static void nearby(Team team, float x, float y, float radius, Consumer<Unit> cons){
public static void nearby(Team team, float x, float y, float radius, Cons<Unit> cons){
unitGroups[team.ordinal()].intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.withinDst(x, y, radius)){
cons.accept(unit);
cons.get(unit);
}
});
playerGroup.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.getTeam() == team && unit.withinDst(x, y, radius)){
cons.accept(unit);
cons.get(unit);
}
});
}
/** Iterates over all units in a rectangle. */
public static void nearby(float x, float y, float width, float height, Consumer<Unit> cons){
public static void nearby(float x, float y, float width, float height, Cons<Unit> cons){
for(Team team : Team.all){
unitGroups[team.ordinal()].intersect(x, y, width, height, cons);
}
@@ -190,12 +190,12 @@ public class Units{
}
/** Iterates over all units in a rectangle. */
public static void nearby(Rectangle rect, Consumer<Unit> cons){
public static void nearby(Rectangle rect, Cons<Unit> cons){
nearby(rect.x, rect.y, rect.width, rect.height, cons);
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Consumer<Unit> cons){
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unit> cons){
EnumSet<Team> targets = state.teams.enemiesOf(team);
for(Team other : targets){
@@ -204,18 +204,18 @@ public class Units{
playerGroup.intersect(x, y, width, height, player -> {
if(targets.contains(player.getTeam())){
cons.accept(player);
cons.get(player);
}
});
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, Rectangle rect, Consumer<Unit> cons){
public static void nearbyEnemies(Team team, Rectangle rect, Cons<Unit> cons){
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
}
/** Iterates over all units. */
public static void all(Consumer<Unit> cons){
public static void all(Cons<Unit> cons){
for(Team team : Team.all){
unitGroups[team.ordinal()].all().each(cons);
}

View File

@@ -20,6 +20,10 @@ public class ArtilleryBulletType extends BasicBulletType{
hitSound = Sounds.explosion;
}
public ArtilleryBulletType(){
this(1f, 1f, "shell");
}
@Override
public void update(io.anuke.mindustry.entities.type.Bullet b){
super.update(b);

View File

@@ -22,6 +22,11 @@ public class BasicBulletType extends BulletType{
this.bulletSprite = bulletSprite;
}
/** For mods. */
public BasicBulletType(){
this(1f, 1f, "bullet");
}
@Override
public void load(){
backRegion = Core.atlas.find(bulletSprite + "-back");

View File

@@ -17,4 +17,8 @@ public class BombBulletType extends BasicBulletType{
collidesAir = false;
hitSound = Sounds.explosion;
}
public BombBulletType(){
this(1f, 1f, "shell");
}
}

View File

@@ -3,12 +3,12 @@ package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.audio.*;
import io.anuke.arc.math.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
@@ -47,7 +47,7 @@ public abstract class BulletType extends Content{
/** Status effect applied on hit. */
public StatusEffect status = StatusEffects.none;
/** Intensity of applied status effect in terms of duration. */
public float statusDuration = 60 * 1f;
public float statusDuration = 60 * 10f;
/** Whether this bullet type collides with tiles. */
public boolean collidesTiles = true;
/** Whether this bullet type collides with tiles that are of the same team. */

View File

@@ -6,7 +6,7 @@ import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.type.Bullet;
public abstract class FlakBulletType extends BasicBulletType{
public class FlakBulletType extends BasicBulletType{
protected static Rectangle rect = new Rectangle();
protected float explodeRange = 30f;
@@ -19,6 +19,10 @@ public abstract class FlakBulletType extends BasicBulletType{
bulletHeight = 10f;
}
public FlakBulletType(){
this(1f, 1f);
}
@Override
public void update(Bullet b){
super.update(b);

View File

@@ -22,6 +22,10 @@ public class HealBulletType extends BulletType{
collidesTeam = true;
}
public HealBulletType(){
this(1f, 1f);
}
@Override
public boolean collides(Bullet b, Tile tile){
return tile.getTeam() != b.getTeam() || tile.entity.healthf() < 1f;

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
@@ -13,14 +14,17 @@ import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.*;
public class LiquidBulletType extends BulletType{
Liquid liquid;
@NonNull Liquid liquid;
public LiquidBulletType(Liquid liquid){
public LiquidBulletType(@Nullable Liquid liquid){
super(3.5f, 0);
this.liquid = liquid;
if(liquid != null){
this.liquid = liquid;
this.status = liquid.effect;
}
lifetime = 74f;
status = liquid.effect;
statusDuration = 90f;
despawnEffect = Fx.none;
hitEffect = Fx.hitLiquid;
@@ -30,13 +34,17 @@ public class LiquidBulletType extends BulletType{
knockback = 0.55f;
}
public LiquidBulletType(){
this(null);
}
@Override
public float range(){
return speed * lifetime / 2f;
}
@Override
public void update(io.anuke.mindustry.entities.type.Bullet b){
public void update(Bullet b){
super.update(b);
if(liquid.canExtinguish()){
@@ -50,7 +58,7 @@ public class LiquidBulletType extends BulletType{
}
@Override
public void draw(io.anuke.mindustry.entities.type.Bullet b){
public void draw(Bullet b){
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, 0.5f + b.fout() * 2.5f);

View File

@@ -23,6 +23,10 @@ public class MissileBulletType extends BasicBulletType{
hitSound = Sounds.explosion;
}
public MissileBulletType(){
this(1f, 1f, "missile");
}
@Override
public void update(Bullet b){
super.update(b);
@@ -32,7 +36,7 @@ public class MissileBulletType extends BasicBulletType{
}
if(weaveMag > 0){
b.velocity().rotate(Mathf.sin(Time.time() + b.id * 4422, weaveScale, weaveMag));
b.velocity().rotate(Mathf.sin(Time.time() + b.id * 4422, weaveScale, weaveMag) * Time.delta());
}
}
}

View File

@@ -12,8 +12,8 @@ import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.TimedEntity;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.type.TypeID;
import io.anuke.mindustry.world.*;
import java.io.*;

View File

@@ -20,8 +20,9 @@ import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.world.Tile;
import static io.anuke.mindustry.Vars.bulletGroup;
import static io.anuke.mindustry.Vars.*;
public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
public static final float lifetime = 10f;
@@ -34,7 +35,7 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
private static final float hitRange = 30f;
private static int lastSeed = 0;
private Array<Position> lines = new Array<>();
private Array<Vector2> lines = new Array<>();
private Color color = Pal.lancerLaser;
/** For pooling use only. Do not call directly! */
@@ -61,10 +62,30 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
random.setSeed(seed);
hit.clear();
boolean[] bhit = {false};
for(int i = 0; i < length / 2; i++){
Bullet.create(Bullets.damageLightning, l, team, x, y, 0f, 1f, 1f, dmg);
l.lines.add(new Vector2(x + Mathf.range(3f), y + Mathf.range(3f)));
if(l.lines.size > 1){
bhit[0] = false;
Position from = l.lines.get(l.lines.size - 2);
Position to = l.lines.get(l.lines.size - 1);
world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
Tile tile = world.ltile(wx, wy);
if(tile != null && tile.block().insulated){
bhit[0] = true;
//snap it instead of removing
l.lines.get(l.lines.size -1).set(wx * tilesize, wy * tilesize);
return true;
}
return false;
});
if(bhit[0]) break;
}
rect.setSize(hitRange).setCenter(x, y);
entities.clear();
if(hit.size < maxChain){
@@ -83,6 +104,7 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
y = furthest.y;
}else{
rotation += random.range(20f);
x += Angles.trnsx(rotation, hitRange / 2f);
y += Angles.trnsy(rotation, hitRange / 2f);
}

View File

@@ -16,7 +16,7 @@ import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.SolidEntity;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.TypeID;
import io.anuke.mindustry.type.TypeID;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.type.Liquid;
import io.anuke.mindustry.world.Tile;

View File

@@ -6,8 +6,8 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.*;
@@ -104,7 +104,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
if(current.breaking){
entity.deconstruct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier)){
if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier, current.hasConfig)){
if(current.hasConfig){
Call.onTileConfig(null, tile, current.config);
}
@@ -188,16 +188,30 @@ public interface BuilderTrait extends Entity, TeamTrait{
/** Add another build requests to the tail of the queue, if it doesn't exist there yet. */
default void addBuildRequest(BuildRequest place){
addBuildRequest(place, true);
}
/** Add another build requests to the queue, if it doesn't exist there yet. */
default void addBuildRequest(BuildRequest place, boolean tail){
BuildRequest replace = null;
for(BuildRequest request : buildQueue()){
if(request.x == place.x && request.y == place.y){
return;
replace = request;
break;
}
}
if(replace != null){
buildQueue().remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.entity instanceof BuildEntity){
place.progress = tile.<BuildEntity>entity().progress;
}
buildQueue().addLast(place);
if(tail){
buildQueue().addLast(place);
}else{
buildQueue().addFirst(place);
}
}
/**
@@ -258,14 +272,26 @@ public interface BuilderTrait extends Entity, TeamTrait{
/** Class for storing build requests. Can be either a place or remove request. */
class BuildRequest{
public final int x, y, rotation;
public final Block block;
public final boolean breaking;
/** Position and rotation of this request. */
public int x, y, rotation;
/** Block being placed. If null, this is a breaking request.*/
public @Nullable Block block;
/** Whether this is a break request.*/
public boolean breaking;
/** Whether this request comes with a config int. If yes, any blocks placed with this request will not call playerPlaced.*/
public boolean hasConfig;
/** Config int. Not used unless hasConfig is true.*/
public int config;
/** Original position, only used in schematics.*/
public int originalX, originalY, originalWidth, originalHeight;
/** Last progress.*/
public float progress;
public boolean initialized;
/** Whether construction has started for this request.*/
public boolean initialized, worldContext = true;
/** Visual scale. Used only for rendering.*/
public float animScale = 0f;
/** This creates a build request. */
public BuildRequest(int x, int y, int rotation, Block block){
@@ -285,13 +311,67 @@ public interface BuilderTrait extends Entity, TeamTrait{
this.breaking = true;
}
public BuildRequest(){
}
public BuildRequest copy(){
BuildRequest copy = new BuildRequest();
copy.x = x;
copy.y = y;
copy.rotation = rotation;
copy.block = block;
copy.breaking = breaking;
copy.hasConfig = hasConfig;
copy.config = config;
copy.originalX = originalX;
copy.originalY = originalY;
copy.progress = progress;
copy.initialized = initialized;
copy.animScale = animScale;
return copy;
}
public BuildRequest original(int x, int y, int originalWidth, int originalHeight){
originalX = x;
originalY = y;
this.originalWidth = originalWidth;
this.originalHeight = originalHeight;
return this;
}
public Rectangle bounds(Rectangle rect){
if(breaking){
return rect.set(-100f, -100f, 0f, 0f);
}else{
return block.bounds(x, y, rect);
}
}
public BuildRequest set(int x, int y, int rotation, Block block){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.breaking = false;
return this;
}
public float drawx(){
return x*tilesize + block.offset();
}
public float drawy(){
return y*tilesize + block.offset();
}
public BuildRequest configure(int config){
this.config = config;
this.hasConfig = true;
return this;
}
public Tile tile(){
public @Nullable Tile tile(){
return world.tile(x, y);
}

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.game.TypeID;
import io.anuke.mindustry.type.TypeID;
public interface TypeTrait{

View File

@@ -1,5 +1,6 @@
package io.anuke.mindustry.entities.type;
import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.Entity;
@@ -14,6 +15,14 @@ public abstract class BaseEntity implements Entity{
id = lastid++;
}
public int tileX(){
return Vars.world.toTile(x);
}
public int tileY(){
return Vars.world.toTile(y);
}
@Override
public int getID(){
return id;

View File

@@ -16,6 +16,8 @@ import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.defense.DeflectorWall.*;

View File

@@ -21,7 +21,6 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.InputHandler.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.*;
@@ -51,7 +50,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public String name = "noname";
public @Nullable
String uuid, usid;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping, isBuilding = true;
public boolean buildWasAutoPaused = false;
public float boostHeat, shootHeat, destructTime;
public boolean achievedFlight;
public Color color = new Color();
@@ -359,7 +359,13 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public void drawOver(){
if(dead) return;
drawMechanics();
if(isBuilding() && isBuilding){
if(!state.isPaused()){
drawBuilding();
}
}else{
drawMining();
}
}
@Override
@@ -426,57 +432,17 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
public void drawBuildRequests(){
BuildRequest last = null;
if(!isLocal) return;
for(BuildRequest request : buildQueue()){
if(request.progress > 0.01f || (buildRequest() == request && request.initialized && (dst(request.x * tilesize, request.y * tilesize) <= placeDistance || state.isEditor()))) continue;
request.animScale = 1f;
if(request.breaking){
Block block = world.ltile(request.x, request.y).block();
//draw removal request
Lines.stroke(2f, Pal.removeBack);
float rad = Mathf.absin(Time.time(), 7f, 1f) + block.size * tilesize / 2f - 1;
Lines.square(
request.x * tilesize + block.offset(),
request.y * tilesize + block.offset() - 1,
rad);
Draw.color(Pal.remove);
Lines.square(
request.x * tilesize + block.offset(),
request.y * tilesize + block.offset(), rad);
control.input.drawBreaking(request);
}else{
Draw.color();
PlaceDraw draw = PlaceDraw.instance;
draw.scalex = 1;
draw.scaley = 1;
draw.rotation = request.rotation;
if(last == null){
request.block.getPlaceDraw(draw, request.rotation, request.x, request.y, request.rotation);
}else{
request.block.getPlaceDraw(draw, request.rotation, last.x - request.x, last.y - request.y, last.rotation);
}
TextureRegion region = draw.region;
Draw.rect(region,
request.x * tilesize + request.block.offset(), request.y * tilesize + request.block.offset(),
region.getWidth() * 1f * Draw.scl * draw.scalex,
region.getHeight() * 1f * Draw.scl * draw.scaley, request.block.rotate ? draw.rotation * 90 : 0);
Draw.color(Pal.accent);
for(int i = 0; i < 4; i++){
Point2 p = Geometry.d8edge[i];
float offset = -Math.max(request.block.size - 1, 0) / 2f * tilesize;
Draw.rect("block-select", request.x * tilesize + request.block.offset() + offset * p.x, request.y * tilesize + request.block.offset() + offset * p.y, i * 90);
}
Draw.color();
last = request;
request.block.drawRequest(request, control.input.allRequests(),
Build.validPlace(getTeam(), request.x, request.y, request.block, request.rotation) || control.input.requestMatches(request));
}
}
@@ -487,6 +453,18 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
//region update methods
@Override
public void updateMechanics(){
if(isBuilding){
updateBuilding();
}
//mine only when not building
if(buildRequest() == null || !isBuilding){
updateMining();
}
}
@Override
public void update(){
hitTime -= Time.delta();
@@ -519,7 +497,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
}
BuildRequest request = buildRequest();
if(isBuilding() && request.tile() != null && (request.tile().withinDst(x, y, placeDistance) || state.isEditor())){
if(isBuilding() && isBuilding && request.tile() != null && (request.tile().withinDst(x, y, placeDistance) || state.isEditor())){
loops.play(Sounds.build, request.tile(), 0.75f);
}
@@ -611,7 +589,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
float xa = Core.input.axis(Binding.move_x);
float ya = Core.input.axis(Binding.move_y);
if(!Core.input.keyDown(Binding.gridMode) && !(Core.scene.getKeyboardFocus() instanceof TextField)){
if(!(Core.scene.getKeyboardFocus() instanceof TextField)){
movement.y += ya * speed;
movement.x += xa * speed;
}
@@ -819,9 +797,11 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
placeQueue.clear();
dead = true;
lastText = null;
isBuilding = true;
textFadeTime = 0f;
target = null;
moveTarget = null;
isShooting = isBoosting = isTransferring = isTyping = false;
spawner = lastSpawner = null;
health = maxHealth();
mining = null;
@@ -912,7 +892,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public void write(DataOutput buffer) throws IOException{
super.writeSave(buffer, !isLocal);
TypeIO.writeStringData(buffer, name);
buffer.writeByte(Pack.byteValue(isAdmin) | (Pack.byteValue(dead) << 1) | (Pack.byteValue(isBoosting) << 2) | (Pack.byteValue(isTyping) << 3));
buffer.writeByte(Pack.byteValue(isAdmin) | (Pack.byteValue(dead) << 1) | (Pack.byteValue(isBoosting) << 2) | (Pack.byteValue(isTyping) << 3)| (Pack.byteValue(isBuilding) << 4));
buffer.writeInt(Color.rgba8888(color));
buffer.writeByte(mech.id);
buffer.writeInt(mining == null ? noSpawner : mining.pos());
@@ -934,6 +914,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
dead = (bools & 2) != 0;
boolean boosting = (bools & 4) != 0;
isTyping = (bools & 8) != 0;
boolean building = (bools & 16) != 0;
color.set(buffer.readInt());
mech = content.getByID(ContentType.mech, buffer.readByte());
int mine = buffer.readInt();
@@ -952,6 +933,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
velocity.y = lastvy;
}else{
mining = world.tile(mine);
isBuilding = building;
isBoosting = boosting;
}

View File

@@ -22,6 +22,7 @@ import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;

View File

@@ -151,7 +151,13 @@ public class FlyingUnit extends BaseUnit{
}
public void drawWeapons(){
for(int i : Mathf.signs){
float tra = rotation - 90, trY = -type.weapon.getRecoil(this, i > 0) + type.weaponOffsetY;
float w = -i * type.weapon.region.getWidth() * Draw.scl;
Draw.rect(type.weapon.region,
x + Angles.trnsx(tra, getWeapon().width * i, trY),
y + Angles.trnsy(tra, getWeapon().width * i, trY), w, type.weapon.region.getHeight() * Draw.scl, rotation - 90);
}
}
public void drawEngine(){

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry.game;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
@@ -61,11 +62,13 @@ public class EventType{
/** Called when a zone's requirements are met. */
public static class ZoneRequireCompleteEvent{
public final Zone zone, required;
public final Zone zoneMet, zoneForMet;
public final Objective objective;
public ZoneRequireCompleteEvent(Zone zone, Zone required){
this.zone = zone;
this.required = required;
public ZoneRequireCompleteEvent(Zone zoneMet, Zone zoneForMet, Objective objective){
this.zoneMet = zoneMet;
this.zoneForMet = zoneForMet;
this.objective = objective;
}
}
@@ -130,7 +133,41 @@ public class EventType{
/** Called when a player deposits items to a block.*/
public static class DepositEvent{
public final Tile tile;
public final Player player;
public final Item item;
public final int amount;
public DepositEvent(Tile tile, Player player, Item item, int amount){
this.tile = tile;
this.player = player;
this.item = item;
this.amount = amount;
}
}
/** Called when the player taps a block. */
public static class TapEvent{
public final Tile tile;
public final Player player;
public TapEvent(Tile tile, Player player){
this.tile = tile;
this.player = player;
}
}
/** Called when the player sets a specific block. */
public static class TapConfigEvent{
public final Tile tile;
public final Player player;
public final int value;
public TapConfigEvent(Tile tile, Player player, int value){
this.tile = tile;
this.player = player;
this.value = value;
}
}
public static class GameOverEvent{

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.game;
import io.anuke.arc.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.mindustry.maps.*;
import static io.anuke.mindustry.Vars.waveTeam;
@@ -44,25 +44,25 @@ public enum Gamemode{
rules.respawnTime = 0f;
});
private final Consumer<Rules> rules;
private final Predicate<Map> validator;
private final Cons<Rules> rules;
private final Boolf<Map> validator;
public final boolean hidden;
public final static Gamemode[] all = values();
Gamemode(Consumer<Rules> rules){
Gamemode(Cons<Rules> rules){
this(false, rules);
}
Gamemode(boolean hidden, Consumer<Rules> rules){
Gamemode(boolean hidden, Cons<Rules> rules){
this(hidden, rules, m -> true);
}
Gamemode(Consumer<Rules> rules, Predicate<Map> validator){
Gamemode(Cons<Rules> rules, Boolf<Map> validator){
this(false, rules, validator);
}
Gamemode(boolean hidden, Consumer<Rules> rules, Predicate<Map> validator){
Gamemode(boolean hidden, Cons<Rules> rules, Boolf<Map> validator){
this.rules = rules;
this.hidden = hidden;
this.validator = validator;
@@ -84,13 +84,13 @@ public enum Gamemode{
/** Applies this preset to this ruleset. */
public Rules apply(Rules in){
rules.accept(in);
rules.get(in);
return in;
}
/** @return whether this mode can be played on the specified map. */
public boolean valid(Map map){
return validator.test(map);
return validator.get(map);
}
public String description(){

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.type.*;
@@ -39,10 +40,14 @@ public class GlobalData{
files.add(Core.settings.getSettingsFile());
files.addAll(customMapDirectory.list());
files.addAll(saveDirectory.list());
files.addAll(screenshotDirectory.list());
files.addAll(modDirectory.list());
files.addAll(schematicDirectory.list());
String base = Core.settings.getDataDirectory().path();
try(OutputStream fos = file.write(false, 2048); ZipOutputStream zos = new ZipOutputStream(fos)){
for(FileHandle add : files){
if(add.isDirectory()) continue;
zos.putNextEntry(new ZipEntry(add.path().substring(base.length())));
Streams.copyStream(add.read(), zos);
zos.closeEntry();
@@ -61,14 +66,8 @@ public class GlobalData{
throw new IllegalArgumentException("Not valid save data.");
}
//purge existing data
for(FileHandle f : base.list()){
if(f.isDirectory()){
f.deleteDirectory();
}else if(!f.name().equals("zipdata.zip")){
f.delete();
}
}
//purge existing tmp data, keep everything else
tmpDirectory.deleteDirectory();
zipped.walk(f -> f.copyTo(base.child(f.path())));
dest.delete();
@@ -91,12 +90,17 @@ public class GlobalData{
state.stats.itemsDelivered.getAndIncrement(item, 0, amount);
}
public boolean hasItems(Array<ItemStack> stacks){
return !stacks.contains(s -> items.get(s.item, 0) < s.amount);
}
public boolean hasItems(ItemStack[] stacks){
for(ItemStack stack : stacks){
if(items.get(stack.item, 0) < stack.amount){
if(!has(stack.item, stack.amount)){
return false;
}
}
return true;
}
@@ -107,6 +111,13 @@ public class GlobalData{
modified = true;
}
public void removeItems(Array<ItemStack> stacks){
for(ItemStack stack : stacks){
items.getAndIncrement(stack.item, 0, -stack.amount);
}
modified = true;
}
public boolean has(Item item, int amount){
return items.get(item, 0) >= amount;
}

View File

@@ -37,7 +37,7 @@ public class LoopControl{
}
}else{
if(data.curVolume <= 0.01f){
sound.stop(data.soundID);
sound.stop();
data.soundID = -1;
return;
}

View File

@@ -22,16 +22,12 @@ public class MusicControl{
public Array<Music> darkMusic = Array.with();
private Music lastRandomPlayed;
private Interval timer = new Interval();
private @Nullable
Music current;
private @Nullable Music current;
private float fade;
private boolean silenced;
public MusicControl(){
Events.on(ClientLoadEvent.class, e -> {
ambientMusic = Array.with(Musics.game1, Musics.game3, Musics.game4, Musics.game6);
darkMusic = Array.with(Musics.game2, Musics.game5, Musics.game7);
});
Events.on(ClientLoadEvent.class, e -> reload());
//only run music 10 seconds after a wave spawns
Events.on(WaveEvent.class, e -> Time.run(60f * 10f, () -> {
@@ -41,6 +37,13 @@ public class MusicControl{
}));
}
private void reload(){
current = null;
fade = 0f;
ambientMusic = Array.with(Musics.game1, Musics.game3, Musics.game4, Musics.game6);
darkMusic = Array.with(Musics.game2, Musics.game5, Musics.game7);
}
/** Update and play the right music track.*/
public void update(){
if(state.is(State.menu)){

View File

@@ -0,0 +1,27 @@
package io.anuke.mindustry.game;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.game.Objectives.*;
import io.anuke.mindustry.type.*;
/** Defines a specific objective for a game. */
public interface Objective{
/** @return whether this objective is met. */
boolean complete();
/** @return the string displayed when this objective is completed, in imperative form.
* e.g. when the objective is 'complete 10 waves', this would display "complete 10 waves".
* If this objective should not be displayed, should return null.*/
@Nullable String display();
/** Build a display for this zone requirement.*/
default void build(Table table){
}
default Zone zone(){
return this instanceof ZoneObjective ? ((ZoneObjective)this).zone : null;
}
}

View File

@@ -0,0 +1,96 @@
package io.anuke.mindustry.game;
import io.anuke.arc.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
/** Holds objective classes. */
public class Objectives{
//TODO
public static class Wave implements Objective{
public int wave;
public Wave(int wave){
this.wave = wave;
}
protected Wave(){}
@Override
public boolean complete(){
return false;
}
@Override
public String display(){
//TODO
return null;
}
}
public static class Unlock implements Objective{
public @NonNull Block block;
public Unlock(Block block){
this.block = block;
}
protected Unlock(){}
@Override
public boolean complete(){
return block.unlocked();
}
@Override
public String display(){
return Core.bundle.format("requirement.unlock", block.localizedName);
}
}
public static class ZoneWave extends ZoneObjective{
public int wave;
public ZoneWave(Zone zone, int wave){
this.zone = zone;
this.wave = wave;
}
protected ZoneWave(){}
@Override
public boolean complete(){
return zone.bestWave() >= wave;
}
@Override
public String display(){
return Core.bundle.format("requirement.wave", wave, zone.localizedName);
}
}
public static class Launched extends ZoneObjective{
public Launched(Zone zone){
this.zone = zone;
}
protected Launched(){}
@Override
public boolean complete(){
return zone.hasLaunched();
}
@Override
public String display(){
return Core.bundle.format("requirement.core", zone.localizedName);
}
}
public abstract static class ZoneObjective implements Objective{
public @NonNull Zone zone;
}
}

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.Serialize;
import io.anuke.arc.collection.Array;
import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.io.JsonIO;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.type.Zone;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.collection.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
/**
* Defines current rules on how the game should function.
@@ -69,6 +69,8 @@ public class Rules{
public boolean tutorial = false;
/** Starting items put in cores */
public Array<ItemStack> loadout = Array.with(ItemStack.with(Items.copper, 100));
/** Blocks that cannot be placed. */
public ObjectSet<Block> bannedBlocks = new ObjectSet<>();
/** Copies this ruleset exactly. Not very efficient at all, do not use often. */
public Rules copy(){

View File

@@ -22,13 +22,12 @@ import java.util.*;
import static io.anuke.mindustry.Vars.*;
public class Saves{
private int nextSlot;
private Array<SaveSlot> saves = new Array<>();
private IntMap<SaveSlot> saveMap = new IntMap<>();
private SaveSlot current;
private AsyncExecutor previewExecutor = new AsyncExecutor(1);
private boolean saving;
private float time;
private FileHandle zoneFile;
private long totalPlaytime;
private long lastTimestamp;
@@ -47,16 +46,13 @@ public class Saves{
public void load(){
saves.clear();
IntArray slots = Core.settings.getObject("save-slots", IntArray.class, IntArray::new);
zoneFile = saveDirectory.child("-1.msav");
for(int i = 0; i < slots.size; i++){
int index = slots.get(i);
if(SaveIO.isSaveValid(index)){
SaveSlot slot = new SaveSlot(index);
for(FileHandle file : saveDirectory.list()){
if(!file.name().contains("backup") && SaveIO.isSaveValid(file)){
SaveSlot slot = new SaveSlot(file);
saves.add(slot);
saveMap.put(slot.index, slot);
slot.meta = SaveIO.getMeta(index);
nextSlot = Math.max(index + 1, nextSlot);
slot.meta = SaveIO.getMeta(file);
}
}
}
@@ -110,73 +106,63 @@ public class Saves{
}
public void zoneSave(){
SaveSlot slot = new SaveSlot(-1);
SaveSlot slot = new SaveSlot(zoneFile);
slot.setName("zone");
saves.remove(s -> s.index == -1);
saves.remove(s -> s.file.equals(zoneFile));
saves.add(slot);
saveMap.put(slot.index, slot);
slot.save();
saveSlots();
}
public SaveSlot addSave(String name){
SaveSlot slot = new SaveSlot(nextSlot);
nextSlot++;
SaveSlot slot = new SaveSlot(getNextSlotFile());
slot.setName(name);
saves.add(slot);
saveMap.put(slot.index, slot);
slot.save();
saveSlots();
return slot;
}
public SaveSlot importSave(FileHandle file) throws IOException{
SaveSlot slot = new SaveSlot(nextSlot);
SaveSlot slot = new SaveSlot(getNextSlotFile());
slot.importFile(file);
nextSlot++;
slot.setName(file.nameWithoutExtension());
saves.add(slot);
saveMap.put(slot.index, slot);
slot.meta = SaveIO.getMeta(slot.index);
slot.meta = SaveIO.getMeta(slot.file);
current = slot;
saveSlots();
return slot;
}
public SaveSlot getZoneSlot(){
SaveSlot slot = getByID(-1);
SaveSlot slot = getSaveSlots().find(s -> s.file.equals(zoneFile));
return slot == null || slot.getZone() == null ? null : slot;
}
public SaveSlot getByID(int id){
return saveMap.get(id);
public FileHandle getNextSlotFile(){
int i = 0;
FileHandle file;
while((file = saveDirectory.child(i + "." + saveExtension)).exists()){
i ++;
}
return file;
}
public Array<SaveSlot> getSaveSlots(){
return saves;
}
private void saveSlots(){
IntArray result = new IntArray(saves.size);
for(int i = 0; i < saves.size; i++) result.add(saves.get(i).index);
Core.settings.putObject("save-slots", result);
Core.settings.save();
}
public class SaveSlot{
public final int index;
//public final int index;
public final FileHandle file;
boolean requestedPreview;
SaveMeta meta;
public SaveSlot(int index){
this.index = index;
public SaveSlot(FileHandle file){
this.file = file;
}
public void load() throws SaveException{
try{
SaveIO.loadFromSlot(index);
meta = SaveIO.getMeta(index);
SaveIO.load(file);
meta = SaveIO.getMeta(file);
current = this;
totalPlaytime = meta.timePlayed;
savePreview();
@@ -190,8 +176,8 @@ public class Saves{
long prev = totalPlaytime;
totalPlaytime = time;
SaveIO.saveToSlot(index);
meta = SaveIO.getMeta(index);
SaveIO.save(file);
meta = SaveIO.getMeta(file);
if(!state.is(State.menu)){
current = this;
}
@@ -226,8 +212,12 @@ public class Saves{
return null;
}
private String index(){
return file.nameWithoutExtension();
}
private FileHandle previewFile(){
return mapPreviewDirectory.child("save_slot_" + index + ".png");
return mapPreviewDirectory.child("save_slot_" + index() + ".png");
}
private FileHandle loadPreviewFile(){
@@ -266,11 +256,11 @@ public class Saves{
}
public String getName(){
return Core.settings.getString("save-" + index + "-name", "untitled");
return Core.settings.getString("save-" + index() + "-name", "untitled");
}
public void setName(String name){
Core.settings.put("save-" + index + "-name", name);
Core.settings.put("save-" + index() + "-name", name);
Core.settings.save();
}
@@ -295,34 +285,33 @@ public class Saves{
}
public boolean isAutosave(){
return Core.settings.getBool("save-" + index + "-autosave", true);
return Core.settings.getBool("save-" + index() + "-autosave", true);
}
public void setAutosave(boolean save){
Core.settings.put("save-" + index + "-autosave", save);
Core.settings.put("save-" + index() + "-autosave", save);
Core.settings.save();
}
public void importFile(FileHandle file) throws IOException{
public void importFile(FileHandle from) throws IOException{
try{
file.copyTo(SaveIO.fileFor(index));
from.copyTo(file);
}catch(Exception e){
throw new IOException(e);
}
}
public void exportFile(FileHandle file) throws IOException{
public void exportFile(FileHandle to) throws IOException{
try{
SaveIO.fileFor(index).copyTo(file);
file.copyTo(to);
}catch(Exception e){
throw new IOException(e);
}
}
public void delete(){
SaveIO.fileFor(index).delete();
file.delete();
saves.removeValue(this, true);
saveMap.remove(index);
if(this == current){
current = null;
}
@@ -330,8 +319,6 @@ public class Saves{
if(Core.assets.isLoaded(loadPreviewFile().path())){
Core.assets.unload(loadPreviewFile().path());
}
saveSlots();
}
}
}

View File

@@ -0,0 +1,126 @@
package io.anuke.mindustry.game;
import io.anuke.arc.collection.*;
import io.anuke.arc.collection.IntIntMap.*;
import io.anuke.arc.files.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.storage.*;
import static io.anuke.mindustry.Vars.*;
public class Schematic implements Publishable, Comparable<Schematic>{
public final Array<Stile> tiles;
public StringMap tags;
public int width, height;
public @Nullable FileHandle file;
public Schematic(Array<Stile> tiles, StringMap tags, int width, int height){
this.tiles = tiles;
this.tags = tags;
this.width = width;
this.height = height;
}
public Array<ItemStack> requirements(){
IntIntMap amounts = new IntIntMap();
tiles.each(t -> {
for(ItemStack stack : t.block.requirements){
amounts.getAndIncrement(stack.item.id, 0, stack.amount);
}
});
Array<ItemStack> stacks = new Array<>();
for(Entry ent : amounts.entries()){
stacks.add(new ItemStack(Vars.content.item(ent.key), ent.value));
}
stacks.sort();
return stacks;
}
public boolean hasCore(){
return tiles.contains(s -> s.block instanceof CoreBlock);
}
public @NonNull CoreBlock findCore(){
CoreBlock block = (CoreBlock)tiles.find(s -> s.block instanceof CoreBlock).block;
if(block == null) throw new IllegalArgumentException("Schematic is missing a core!");
return block;
}
public String name(){
return tags.get("name", "unknown");
}
public void save(){
schematics.saveChanges(this);
}
@Override
public String getSteamID(){
return tags.get("steamid");
}
@Override
public void addSteamID(String id){
tags.put("steamid", id);
save();
}
@Override
public void removeSteamID(){
tags.remove("steamid");
save();
}
@Override
public String steamTitle(){
return name();
}
@Override
public String steamDescription(){
return null;
}
@Override
public String steamTag(){
return "schematic";
}
@Override
public FileHandle createSteamFolder(String id){
FileHandle directory = tmpDirectory.child("schematic_" + id).child("schematic." + schematicExtension);
file.copyTo(directory);
return directory;
}
@Override
public FileHandle createSteamPreview(String id){
FileHandle preview = tmpDirectory.child("schematic_preview_" + id + ".png");
schematics.savePreview(this, preview);
return preview;
}
@Override
public int compareTo(Schematic schematic){
return name().compareTo(schematic.name());
}
public static class Stile{
public @NonNull Block block;
public short x, y;
public int config;
public byte rotation;
public Stile(Block block, int x, int y, int config, byte rotation){
this.block = block;
this.x = (short)x;
this.y = (short)y;
this.config = config;
this.rotation = rotation;
}
}
}

View File

@@ -0,0 +1,456 @@
package io.anuke.mindustry.game;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.glutils.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.Streams.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Schematic.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.Placement.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.production.*;
import io.anuke.mindustry.world.blocks.storage.*;
import java.io.*;
import java.util.zip.*;
import static io.anuke.mindustry.Vars.*;
/** Handles schematics.*/
public class Schematics implements Loadable{
public static final String base64Header = "bXNjaAB";
private static final byte[] header = {'m', 's', 'c', 'h'};
private static final byte version = 0;
private static final int padding = 2;
private static final int maxPreviewsMobile = 32;
private static final int resolution = 32;
private OptimizedByteArrayOutputStream out = new OptimizedByteArrayOutputStream(1024);
private Array<Schematic> all = new Array<>();
private OrderedMap<Schematic, FrameBuffer> previews = new OrderedMap<>();
private FrameBuffer shadowBuffer;
private long lastClearTime;
public Schematics(){
Events.on(DisposeEvent.class, e -> {
previews.each((schem, m) -> m.dispose());
previews.clear();
shadowBuffer.dispose();
});
Events.on(ContentReloadEvent.class, event -> {
previews.each((schem, m) -> m.dispose());
previews.clear();
load();
});
}
@Override
public void loadSync(){
load();
}
/** Load all schematics in the folder immediately.*/
public void load(){
all.clear();
for(FileHandle file : schematicDirectory.list()){
loadFile(file);
}
platform.getWorkshopContent(Schematic.class).each(this::loadFile);
all.sort();
if(shadowBuffer == null){
Core.app.post(() -> shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 8, maxSchematicSize + padding + 8));
}
}
public void overwrite(Schematic target, Schematic newSchematic){
if(previews.containsKey(target)){
previews.get(target).dispose();
previews.remove(target);
}
target.tiles.clear();
target.tiles.addAll(newSchematic.tiles);
target.width = newSchematic.width;
target.height = newSchematic.height;
newSchematic.tags.putAll(target.tags);
newSchematic.file = target.file;
try{
write(newSchematic, target.file);
}catch(Exception e){
Log.err(e);
ui.showException(e);
}
}
private void loadFile(FileHandle file){
if(!file.extension().equals(schematicExtension)) return;
try{
Schematic s = read(file);
all.add(s);
//external file from workshop
if(!s.file.parent().equals(schematicDirectory)){
s.tags.put("steamid", s.file.parent().name());
}
}catch(IOException e){
Log.err(e);
}
}
public Array<Schematic> all(){
return all;
}
public void saveChanges(Schematic s){
if(s.file != null){
try{
write(s, s.file);
}catch(Exception e){
ui.showException(e);
}
}
}
public void savePreview(Schematic schematic, FileHandle file){
FrameBuffer buffer = getBuffer(schematic);
Draw.flush();
buffer.begin();
Pixmap pixmap = ScreenUtils.getFrameBufferPixmap(0, 0, buffer.getWidth(), buffer.getHeight());
file.writePNG(pixmap);
buffer.end();
}
public Texture getPreview(Schematic schematic){
return getBuffer(schematic).getTexture();
}
public boolean hasPreview(Schematic schematic){
return previews.containsKey(schematic);
}
public FrameBuffer getBuffer(Schematic schematic){
//dispose unneeded previews to prevent memory outage errors.
//only runs every 2 seconds
if(mobile && Time.timeSinceMillis(lastClearTime) > 1000 * 2 && previews.size > maxPreviewsMobile){
Array<Schematic> keys = previews.orderedKeys().copy();
for(int i = 0; i < previews.size - maxPreviewsMobile; i++){
//dispose and remove unneeded previews
previews.get(keys.get(i)).dispose();
previews.remove(keys.get(i));
}
//update last clear time
lastClearTime = Time.millis();
}
if(!previews.containsKey(schematic)){
Draw.blend();
Draw.reset();
Tmp.m1.set(Draw.proj());
Tmp.m2.set(Draw.trans());
FrameBuffer buffer = new FrameBuffer((schematic.width + padding) * resolution, (schematic.height + padding) * resolution);
shadowBuffer.beginDraw(Color.clear);
Draw.trans().idt();
Draw.proj().setOrtho(0, 0, shadowBuffer.getWidth(), shadowBuffer.getHeight());
Draw.color();
schematic.tiles.each(t -> {
int size = t.block.size;
int offsetx = -(size - 1) / 2;
int offsety = -(size - 1) / 2;
for(int dx = 0; dx < size; dx++){
for(int dy = 0; dy < size; dy++){
int wx = t.x + dx + offsetx;
int wy = t.y + dy + offsety;
Fill.square(padding/2f + wx + 0.5f, padding/2f + wy + 0.5f, 0.5f);
}
}
});
shadowBuffer.endDraw();
buffer.beginDraw(Color.clear);
Draw.proj().setOrtho(0, buffer.getHeight(), buffer.getWidth(), -buffer.getHeight());
Tmp.tr1.set(shadowBuffer.getTexture(), 0, 0, schematic.width + padding, schematic.height + padding);
Draw.color(0f, 0f, 0f, 1f);
Draw.rect(Tmp.tr1, buffer.getWidth()/2f, buffer.getHeight()/2f, buffer.getWidth(), -buffer.getHeight());
Draw.color();
Array<BuildRequest> requests = schematic.tiles.map(t -> new BuildRequest(t.x, t.y, t.rotation, t.block).configure(t.config));
Draw.flush();
//scale each request to fit schematic
Draw.trans().scale(resolution / tilesize, resolution / tilesize).translate(tilesize*1.5f, tilesize*1.5f);
//draw requests
requests.each(req -> {
req.animScale = 1f;
req.worldContext = false;
req.block.drawRequestRegion(req, requests::each);
});
requests.each(req -> req.block.drawRequestConfigTop(req, requests::each));
Draw.flush();
Draw.trans().idt();
buffer.endDraw();
Draw.proj(Tmp.m1);
Draw.trans(Tmp.m2);
previews.put(schematic, buffer);
}
return previews.get(schematic);
}
/** Creates an array of build requests from a schematic's data, centered on the provided x+y coordinates. */
public Array<BuildRequest> toRequests(Schematic schem, int x, int y){
return schem.tiles.map(t -> new BuildRequest(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block).original(t.x, t.y, schem.width, schem.height).configure(t.config))
.removeAll(s -> !s.block.isVisible() || !s.block.unlockedCur());
}
public void placeLoadout(Schematic schem, int x, int y){
Stile coreTile = schem.tiles.find(s -> s.block instanceof CoreBlock);
int ox = x - coreTile.x, oy = y - coreTile.y;
schem.tiles.each(st -> {
Tile tile = world.tile(st.x + ox, st.y + oy);
if(tile == null) return;
world.setBlock(tile, st.block, defaultTeam);
tile.rotation(st.rotation);
if(st.block.posConfig){
tile.configureAny(Pos.get(tile.x - st.x + Pos.x(st.config), tile.y - st.y + Pos.y(st.config)));
}else{
tile.configureAny(st.config);
}
if(st.block instanceof Drill){
tile.getLinkedTiles(t -> t.setOverlay(Blocks.oreCopper));
}
});
}
/** Adds a schematic to the list, also copying it into the files.*/
public void add(Schematic schematic){
all.add(schematic);
try{
FileHandle file = schematicDirectory.child(Time.millis() + "." + schematicExtension);
write(schematic, file);
schematic.file = file;
}catch(Exception e){
ui.showException(e);
Log.err(e);
}
}
public void remove(Schematic s){
all.remove(s);
if(s.file != null){
s.file.delete();
}
if(previews.containsKey(s)){
previews.get(s).dispose();
previews.remove(s);
}
}
/** Creates a schematic from a world selection. */
public Schematic create(int x, int y, int x2, int y2){
NormalizeResult result = Placement.normalizeArea(x, y, x2, y2, 0, false, maxSchematicSize);
x = result.x;
y = result.y;
x2 = result.x2;
y2 = result.y2;
int ox = x, oy = y, ox2 = x2, oy2 = y2;
Array<Stile> tiles = new Array<>();
int minx = x2, miny = y2, maxx = x, maxy = y;
boolean found = false;
for(int cx = x; cx <= x2; cx++){
for(int cy = y; cy <= y2; cy++){
Tile linked = world.ltile(cx, cy);
if(linked != null && linked.entity != null && linked.entity.block.isVisible() && !(linked.block() instanceof BuildBlock)){
int top = linked.block().size/2;
int bot = linked.block().size % 2 == 1 ? -linked.block().size/2 : -(linked.block().size - 1)/2;
minx = Math.min(linked.x + bot, minx);
miny = Math.min(linked.y + bot, miny);
maxx = Math.max(linked.x + top, maxx);
maxy = Math.max(linked.y + top, maxy);
found = true;
}
}
}
if(found){
x = minx;
y = miny;
x2 = maxx;
y2 = maxy;
}else{
return new Schematic(new Array<>(), new StringMap(), 1, 1);
}
int width = x2 - x + 1, height = y2 - y + 1;
int offsetX = -x, offsetY = -y;
IntSet counted = new IntSet();
for(int cx = ox; cx <= ox2; cx++){
for(int cy = oy; cy <= oy2; cy++){
Tile tile = world.ltile(cx, cy);
if(tile != null && tile.entity != null && !counted.contains(tile.pos()) && !(tile.block() instanceof BuildBlock) && tile.entity.block.isVisible()){
int config = tile.entity.config();
if(tile.block().posConfig){
config = Pos.get(Pos.x(config) + offsetX, Pos.y(config) + offsetY);
}
tiles.add(new Stile(tile.block(), tile.x + offsetX, tile.y + offsetY, config, tile.rotation()));
counted.add(tile.pos());
}
}
}
return new Schematic(tiles, new StringMap(), width, height);
}
/** Converts a schematic to base64. Note that the result of this will always start with 'bXNjaAB'.*/
public String writeBase64(Schematic schematic){
try{
out.reset();
write(schematic, out);
return new String(Base64Coder.encode(out.getBuffer(), out.size()));
}catch(IOException e){
throw new RuntimeException(e);
}
}
//region IO methods
/** Loads a schematic from base64. May throw an exception. */
public static Schematic readBase64(String schematic) throws IOException{
return read(new ByteArrayInputStream(Base64Coder.decode(schematic)));
}
public static Schematic read(FileHandle file) throws IOException{
Schematic s = read(new DataInputStream(file.read(1024)));
if(!s.tags.containsKey("name")){
s.tags.put("name", file.nameWithoutExtension());
}
s.file = file;
return s;
}
public static Schematic read(InputStream input) throws IOException{
for(byte b : header){
if(input.read() != b){
throw new IOException("Not a schematic file (missing header).");
}
}
int ver;
if((ver = input.read()) != version){
throw new IOException("Unknown version: " + ver);
}
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
short width = stream.readShort(), height = stream.readShort();
StringMap map = new StringMap();
byte tags = stream.readByte();
for(int i = 0; i < tags; i++){
map.put(stream.readUTF(), stream.readUTF());
}
IntMap<Block> blocks = new IntMap<>();
byte length = stream.readByte();
for(int i = 0; i < length; i++){
Block block = Vars.content.getByName(ContentType.block, stream.readUTF());
blocks.put(i, block == null ? Blocks.air : block);
}
int total = stream.readInt();
Array<Stile> tiles = new Array<>(total);
for(int i = 0; i < total; i++){
Block block = blocks.get(stream.readByte());
int position = stream.readInt();
int config = stream.readInt();
byte rotation = stream.readByte();
if(block != Blocks.air){
tiles.add(new Stile(block, Pos.x(position), Pos.y(position), config, rotation));
}
}
return new Schematic(tiles, map, width, height);
}
}
public static void write(Schematic schematic, FileHandle file) throws IOException{
write(schematic, file.write(false, 1024));
}
public static void write(Schematic schematic, OutputStream output) throws IOException{
output.write(header);
output.write(version);
try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){
stream.writeShort(schematic.width);
stream.writeShort(schematic.height);
stream.writeByte(schematic.tags.size);
for(ObjectMap.Entry<String, String> e : schematic.tags.entries()){
stream.writeUTF(e.key);
stream.writeUTF(e.value);
}
OrderedSet<Block> blocks = new OrderedSet<>();
schematic.tiles.each(t -> blocks.add(t.block));
//create dictionary
stream.writeByte(blocks.size);
for(int i = 0; i < blocks.size; i++){
stream.writeUTF(blocks.orderedItems().get(i).name);
}
stream.writeInt(schematic.tiles.size);
//write each tile
for(Stile tile : schematic.tiles){
stream.writeByte(blocks.orderedItems().indexOf(tile.block));
stream.writeInt(Pos.get(tile.x, tile.y));
stream.writeInt(tile.config);
stream.writeByte(tile.rotation);
}
}
}
//endregion
}

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.game;
import io.anuke.arc.util.serialization.Json;
import io.anuke.arc.util.serialization.Json.Serializable;
import io.anuke.arc.util.serialization.JsonValue;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.type.*;
@@ -84,6 +85,7 @@ public class SpawnGroup implements Serializable{
@Override
public void read(Json json, JsonValue data){
type = content.getByName(ContentType.unit, data.getString("type", "dagger"));
if(type == null) type = UnitTypes.dagger;
begin = data.getInt("begin", 0);
end = data.getInt("end", never);
spacing = data.getInt("spacing", 1);

View File

@@ -33,7 +33,7 @@ public class Stats{
score += (float)((wavesLasted - zone.conditionWave) / zone.launchPeriod + 1) * 1.2f;
}
int capacity = zone.loadout.core().itemCapacity;
int capacity = zone.loadout.findCore().itemCapacity;
//weigh used fractions
float frac = 0f;

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.game;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.*;
@@ -40,6 +40,12 @@ public class Tutorial{
Events.on(BlockInfoEvent.class, event -> events.add("blockinfo"));
Events.on(DepositEvent.class, event -> events.add("deposit"));
Events.on(WithdrawEvent.class, event -> events.add("withdraw"));
Events.on(ClientLoadEvent.class, e -> {
for(TutorialStage stage : TutorialStage.values()){
stage.load();
}
});
}
/** update tutorial state, transition if needed */
@@ -188,29 +194,33 @@ public class Tutorial{
},;
protected String line = "";
protected final Function<String, String> text;
protected final Func<String, String> text;
protected Array<String> sentences;
protected final BooleanProvider done;
protected final Boolp done;
TutorialStage(Function<String, String> text, BooleanProvider done){
TutorialStage(Func<String, String> text, Boolp done){
this.text = text;
this.done = done;
}
TutorialStage(BooleanProvider done){
TutorialStage(Boolp done){
this(line -> line, done);
}
/** displayed tutorial stage text.*/
public String text(){
if(sentences == null){
this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
load();
}
String line = sentences.get(control.tutorial.sentence);
return line.contains("{") ? text.get(line) : line;
}
void load(){
this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
}
/** called every frame when this stage is active.*/
void update(){

View File

@@ -1,19 +0,0 @@
package io.anuke.mindustry.game;
import io.anuke.arc.function.Supplier;
import io.anuke.mindustry.entities.traits.TypeTrait;
import io.anuke.mindustry.type.ContentType;
public class TypeID extends MappableContent{
public final Supplier<? extends TypeTrait> constructor;
public TypeID(String name, Supplier<? extends TypeTrait> constructor){
super(name);
this.constructor = constructor;
}
@Override
public ContentType getContentType(){
return ContentType.typeid;
}
}

View File

@@ -9,12 +9,11 @@ import io.anuke.arc.graphics.glutils.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.base.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.Block.*;
import static io.anuke.arc.Core.camera;
import static io.anuke.mindustry.Vars.*;
@@ -30,6 +29,7 @@ public class BlockRenderer implements Disposable{
private int lastCamX, lastCamY, lastRangeX, lastRangeY;
private int requestidx = 0;
private int iterateidx = 0;
private float brokenFade = 0f;
private FrameBuffer shadows = new FrameBuffer(2, 2);
private FrameBuffer fog = new FrameBuffer(2, 2);
private Array<Tile> outArray = new Array<>();
@@ -124,14 +124,22 @@ public class BlockRenderer implements Disposable{
Draw.shader();
}
public void drawBroken(){
if(unitGroups[player.getTeam().ordinal()].all().contains(p -> p instanceof BuilderDrone)){
public void drawDestroyed(){
if(!Core.settings.getBool("destroyedblocks")) return;
if(control.input.isPlacing() || control.input.isBreaking()){
brokenFade = Mathf.lerpDelta(brokenFade, 1f, 0.1f);
}else{
brokenFade = Mathf.lerpDelta(brokenFade, 0f, 0.1f);
}
if(brokenFade > 0.001f){
for(BrokenBlock block : state.teams.get(player.getTeam()).brokenBlocks){
Block b = content.block(block.block);
if(!camera.bounds(Tmp.r1).grow(tilesize * 2f).overlaps(Tmp.r2.setSize(b.size * tilesize).setCenter(block.x * tilesize + b.offset(), block.y * tilesize + b.offset()))) continue;
Draw.alpha(0.5f);
Draw.mixcol(Pal.accent, 0.2f + Mathf.absin(5f, 0.2f));
Draw.alpha(0.53f * brokenFade);
Draw.mixcol(Color.white, 0.2f + Mathf.absin(Time.globalTime(), 6f, 0.2f));
Draw.rect(b.icon(Cicon.full), block.x * tilesize + b.offset(), block.y * tilesize + b.offset(), b.rotate ? block.rotation * 90 : 0f);
}
Draw.reset();
@@ -242,28 +250,34 @@ public class BlockRenderer implements Disposable{
}
public void drawBlocks(Layer stopAt){
int startIdx = iterateidx;
for(; iterateidx < requestidx; iterateidx++){
BlockRequest request = requests.get(iterateidx);
if(iterateidx < requests.size && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){
if(request.layer.ordinal() > stopAt.ordinal()){
break;
}
BlockRequest req = requests.get(iterateidx);
Block block = req.tile.block();
if(request.layer == Layer.power){
if(iterateidx - startIdx > 0 && request.tile.pos() == requests.get(iterateidx - 1).tile.pos()){
continue;
}
}
if(req.layer == Layer.block){
block.draw(req.tile);
if(req.tile.entity != null && req.tile.entity.damaged()){
block.drawCracks(req.tile);
Block block = request.tile.block();
if(request.layer == Layer.block){
block.draw(request.tile);
if(request.tile.entity != null && request.tile.entity.damaged()){
block.drawCracks(request.tile);
}
if(block.synthetic() && req.tile.getTeam() != player.getTeam()){
block.drawTeam(req.tile);
if(block.synthetic() && request.tile.getTeam() != player.getTeam()){
block.drawTeam(request.tile);
}
}else if(req.layer == block.layer){
block.drawLayer(req.tile);
}else if(req.layer == block.layer2){
block.drawLayer2(req.tile);
}else if(request.layer == block.layer){
block.drawLayer(request.tile);
}else if(request.layer == block.layer2){
block.drawLayer2(request.tile);
}
}
}
@@ -328,7 +342,9 @@ public class BlockRenderer implements Disposable{
@Override
public int compareTo(BlockRequest other){
return layer.compareTo(other.layer);
int compare = layer.compareTo(other.layer);
return (compare != 0) ? compare : Integer.compare(tile.pos(), other.tile.pos());
}
@Override

View File

@@ -2,7 +2,7 @@ package io.anuke.mindustry.graphics;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.function.PositionConsumer;
import io.anuke.arc.func.Floatc2;
import io.anuke.arc.graphics.Camera;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.*;
@@ -14,8 +14,8 @@ import io.anuke.arc.util.noise.RidgedPerlin;
import io.anuke.arc.util.noise.Simplex;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.UnitTypes;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.UnitType;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
@@ -282,7 +282,7 @@ public class MenuRenderer implements Disposable{
});
}
private void flyers(PositionConsumer cons){
private void flyers(Floatc2 cons){
float tw = width * tilesize * 1f + tilesize;
float th = height * tilesize * 1f + tilesize;
float range = 500f;
@@ -291,7 +291,7 @@ public class MenuRenderer implements Disposable{
for(int i = 0; i < flyers; i++){
Tmp.v1.trns(flyerRot, time * (2f + flyerType.speed));
cons.accept((Mathf.randomSeedRange(i, range) + Tmp.v1.x + Mathf.absin(time + Mathf.randomSeedRange(i + 2, 500), 10f, 3.4f) + offset) % (tw + Mathf.randomSeed(i + 5, 0, 500)),
cons.get((Mathf.randomSeedRange(i, range) + Tmp.v1.x + Mathf.absin(time + Mathf.randomSeedRange(i + 2, 500), 10f, 3.4f) + offset) % (tw + Mathf.randomSeed(i + 5, 0, 500)),
(Mathf.randomSeedRange(i + 1, range) + Tmp.v1.y + Mathf.absin(time + Mathf.randomSeedRange(i + 3, 500), 10f, 3.4f) + offset) % th);
}
}

View File

@@ -9,10 +9,12 @@ import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.*;
@@ -68,7 +70,7 @@ public class MinimapRenderer implements Disposable{
region = new TextureRegion(texture);
}
public void drawEntities(float x, float y, float w, float h){
public void drawEntities(float x, float y, float w, float h, boolean withLabels){
updateUnitArray();
float sz = baseSize * zoom;
@@ -80,7 +82,17 @@ public class MinimapRenderer implements Disposable{
rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize);
for(Unit unit : units){
float rx = (unit.x - rect.x) / rect.width * w, ry = (unit.y - rect.y) / rect.width * h;
float rx = (unit.x - rect.x) / rect.width * w;
float ry = (unit.y - rect.y) / rect.width * h;
if(withLabels && unit instanceof Player){
Player pl = (Player) unit;
if(!pl.isLocal){
// Only display names for other players.
drawLabel(x + rx, y + ry, pl.name, unit.getTeam().color);
}
}
Draw.color(unit.getTeam().color);
Fill.rect(x + rx, y + ry, Scl.scl(baseSize / 2f), Scl.scl(baseSize / 2f));
}
@@ -88,6 +100,10 @@ public class MinimapRenderer implements Disposable{
Draw.color();
}
public void drawEntities(float x, float y, float w, float h){
drawEntities(x, y, w, h, true);
}
public TextureRegion getRegion(){
if(texture == null) return null;
@@ -133,6 +149,10 @@ public class MinimapRenderer implements Disposable{
private int colorFor(Tile tile){
if(tile == null) return 0;
tile = tile.link();
int bc = tile.block().minimapColor(tile);
if(bc != 0){
return bc;
}
return Tmp.c1.set(MapIO.colorFor(tile.floor(), tile.block(), tile.overlay(), tile.getTeam())).mul(tile.block().cacheLayer == CacheLayer.walls ? 1f - tile.rotation() / 4f : 1f).rgba();
}
@@ -145,4 +165,27 @@ public class MinimapRenderer implements Disposable{
pixmap = null;
}
}
public void drawLabel(float x, float y, String text, Color color){
BitmapFont font = Fonts.outline;
GlyphLayout l = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
boolean ints = font.usesIntegerPositions();
font.getData().setScale(1 / 1.5f / Scl.scl(1f));
font.setUseIntegerPositions(false);
l.setText(font, text, color, 90f, Align.left, true);
float yOffset = 20f;
float margin = 3f;
Draw.color(0f, 0f, 0f, 0.2f);
Fill.rect(x, y + yOffset - l.height/2f, l.width + margin, l.height + margin);
Draw.color();
font.setColor(color);
font.draw(text, x - l.width/2f, y + yOffset, 90f, Align.left, true);
font.setUseIntegerPositions(ints);
font.getData().setScale(1f);
Pools.free(l);
}
}

View File

@@ -12,7 +12,11 @@ import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.type.Category;
import io.anuke.mindustry.ui.Cicon;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.units.MechPad;
import io.anuke.mindustry.world.meta.BlockFlag;
import static io.anuke.mindustry.Vars.*;
@@ -25,9 +29,9 @@ public class OverlayRenderer{
public void drawBottom(){
InputHandler input = control.input;
if(!input.isDrawing() || player.isDead()) return;
if(player.isDead()) return;
input.drawOutlined();
input.drawBottom();
}
public void drawTop(){
@@ -56,6 +60,21 @@ public class OverlayRenderer{
Draw.reset();
}
});
if(ui.hudfrag.blockfrag.currentCategory == Category.upgrade){
for(Tile mechpad : indexer.getAllied(player.getTeam(), BlockFlag.mechPad)){
if(!(mechpad.block() instanceof MechPad)) continue;
if(!rect.setSize(Core.camera.width * 0.9f, Core.camera.height * 0.9f)
.setCenter(Core.camera.position.x, Core.camera.position.y).contains(mechpad.x, mechpad.y)){
Tmp.v1.set(mechpad.worldx(), mechpad.worldy()).sub(Core.camera.position.x, Core.camera.position.y).setLength(indicatorLength);
Lines.stroke(2f, ((MechPad) mechpad.block()).mech.engineColor);
Lines.lineAngle(Core.camera.position.x + Tmp.v1.x, Core.camera.position.y + Tmp.v1.y, Tmp.v1.angle(), 0.5f);
Draw.reset();
}
}
}
}
if(player.isDead()) return; //dead players don't draw

View File

@@ -75,6 +75,8 @@ public class Pal{
surge = Color.valueOf("f3e979"),
plastanium = Color.valueOf("a1b46e"),
redSpark = Color.valueOf("fbb97f"),
orangeSpark = Color.valueOf("d2b29c"),

View File

@@ -12,13 +12,17 @@ public enum Binding implements KeyBind{
select(KeyCode.MOUSE_LEFT),
deselect(KeyCode.MOUSE_RIGHT),
break_block(KeyCode.MOUSE_RIGHT),
clear_building(KeyCode.Q),
pause_building(KeyCode.E),
rotate(new Axis(KeyCode.SCROLL)),
rotateplaced(KeyCode.R),
diagonal_placement(KeyCode.CONTROL_LEFT),
pick(KeyCode.MOUSE_MIDDLE),
schematic_select(KeyCode.F),
schematic_flip_x(KeyCode.Z),
schematic_flip_y(KeyCode.X),
schematic_menu(KeyCode.T),
dash(KeyCode.SHIFT_LEFT),
gridMode(KeyCode.BACKTICK),
gridModeShift(KeyCode.ALT_LEFT),
zoom_hold(KeyCode.CONTROL_LEFT, "view"),
zoom(new Axis(KeyCode.SCROLL)),
menu(Core.app.getType() == ApplicationType.Android ? KeyCode.BACK : KeyCode.ESCAPE),
@@ -27,12 +31,12 @@ public enum Binding implements KeyBind{
minimap(KeyCode.M),
toggle_menus(KeyCode.C),
screenshot(KeyCode.P),
toggle_power_lines(KeyCode.F7),
player_list(KeyCode.TAB, "multiplayer"),
chat(KeyCode.ENTER),
chat_history_prev(KeyCode.UP),
chat_history_next(KeyCode.DOWN),
chat_scroll(new Axis(KeyCode.SCROLL)),
;
private final KeybindValue defaultValue;

View File

@@ -5,15 +5,19 @@ import io.anuke.arc.Graphics.*;
import io.anuke.arc.Graphics.Cursor.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.PlaceUtils.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.*;
import static io.anuke.arc.Core.scene;
@@ -23,101 +27,105 @@ import static io.anuke.mindustry.input.PlaceMode.*;
public class DesktopInput extends InputHandler{
/** Current cursor type. */
private Cursor cursorType = SystemCursor.arrow;
/** Position where the player started dragging a line. */
private int selectX, selectY;
private int selectX, selectY, schemX, schemY;
/** Last known line positions.*/
private int lastLineX, lastLineY, schematicX, schematicY;
/** Whether selecting mode is active. */
private PlaceMode mode;
/** Animation scale for line. */
private float selectScale;
/** Selected build request for movement. */
private @Nullable BuildRequest sreq;
/** Whether player is currently deleting removal requests. */
private boolean deleting = false;
private int prevX, prevY, prevRotation;
@Override
public void buildUI(Group group){
group.fill(t -> {
t.bottom().update(() -> t.getColor().a = Mathf.lerpDelta(t.getColor().a, player.isBuilding() ? 1f : 0f, 0.15f));
t.visible(() -> Core.settings.getBool("hints") && selectRequests.isEmpty());
t.touchable(() -> t.getColor().a < 0.1f ? Touchable.disabled : Touchable.childrenOnly);
t.table(Styles.black6, b -> {
b.defaults().left();
b.label(() -> Core.bundle.format(!player.isBuilding ? "resumebuilding" : "pausebuilding", Core.keybinds.get(Binding.pause_building).key.toString())).style(Styles.outlineLabel);
b.row();
b.add(Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.toString())).style(Styles.outlineLabel);
b.row();
b.add(Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.toString())).style(Styles.outlineLabel);
}).margin(10f);
});
/** Draws a placement icon for a specific block. */
void drawPlace(int x, int y, Block block, int rotation, int prevX, int prevY, int prevRotation){
if(validPlace(x, y, block, rotation)){
block.getPlaceDraw(placeDraw, rotation, prevX, prevY, prevRotation);
Draw.color();
Draw.mixcol(Pal.accent, 0.12f + Mathf.absin(Time.time(), 8f, 0.35f));
Draw.rect(placeDraw.region, x * tilesize + block.offset(), y * tilesize + block.offset(),
placeDraw.region.getWidth() * selectScale * Draw.scl * placeDraw.scalex,
placeDraw.region.getHeight() * selectScale * Draw.scl * placeDraw.scaley,
block.rotate ? placeDraw.rotation * 90 : 0);
Draw.color(Pal.accent);
for(int i = 0; i < 4; i++){
Point2 p = Geometry.d8edge[i];
float offset = -Math.max(block.size - 1, 0) / 2f * tilesize;
if(i % 2 == 0)
Draw.rect("block-select", x * tilesize + block.offset() + offset * p.x, y * tilesize + block.offset() + offset * p.y, i * 90);
}
Draw.color();
Draw.mixcol();
}else{
Draw.color(Pal.removeBack);
Lines.square(x * tilesize + block.offset(), y * tilesize + block.offset() - 1, block.size * tilesize / 2f - 1);
Draw.color(Pal.remove);
Lines.square(x * tilesize + block.offset(), y * tilesize + block.offset(), block.size * tilesize / 2f - 1);
}
group.fill(t -> {
t.visible(() -> lastSchematic != null && !selectRequests.isEmpty());
t.bottom();
t.table(Styles.black6, b -> {
b.defaults().left();
b.add(Core.bundle.format("schematic.flip",
Core.keybinds.get(Binding.schematic_flip_x).key.toString(),
Core.keybinds.get(Binding.schematic_flip_y).key.toString())).style(Styles.outlineLabel);
b.row();
b.table(a -> {
a.addImageTextButton("$schematic.add", Icon.saveSmall, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null);
});
}).margin(6f);
});
}
@Override
public boolean isDrawing(){
return mode != none || block != null;
}
@Override
public void drawOutlined(){
public void drawTop(){
Lines.stroke(1f);
int cursorX = tileX(Core.input.mouseX());
int cursorY = tileY(Core.input.mouseY());
//draw selection(s)
if(mode == placing && block != null){
prevX = selectX;
prevY = selectY;
prevRotation = rotation;
iterateLine(selectX, selectY, cursorX, cursorY, l -> {
if(l.last && block.rotate){
drawArrow(block, l.x, l.y, l.rotation);
}
drawPlace(l.x, l.y, block, l.rotation, prevX - l.x, prevY - l.y, prevRotation);
prevX = l.x;
prevY = l.y;
prevRotation = l.rotation;
});
}else if(mode == breaking){
NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, selectX, selectY, cursorX, cursorY, false, maxLength, 1f);
NormalizeResult dresult = PlaceUtils.normalizeArea(selectX, selectY, cursorX, cursorY, rotation, false, maxLength);
for(int x = dresult.x; x <= dresult.x2; x++){
for(int y = dresult.y; y <= dresult.y2; y++){
Tile tile = world.ltile(x, y);
if(tile == null || !validBreak(tile.x, tile.y)) continue;
Draw.color(Pal.removeBack);
Lines.square(tile.drawx(), tile.drawy() - 1, tile.block().size * tilesize / 2f - 1);
Draw.color(Pal.remove);
Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f - 1);
for(int i = 0; i < lineRequests.size; i++){
BuildRequest req = lineRequests.get(i);
if(i == lineRequests.size - 1 && req.block.rotate){
drawArrow(block, req.x, req.y, req.rotation);
}
drawRequest(lineRequests.get(i));
}
Draw.color(Pal.removeBack);
Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y);
Draw.color(Pal.remove);
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
}else if(mode == breaking){
drawBreakSelection(selectX, selectY, cursorX, cursorY);
}else if(isPlacing()){
if(block.rotate){
drawArrow(block, cursorX, cursorY, rotation);
}
drawPlace(cursorX, cursorY, block, rotation, cursorX, cursorY, rotation);
Draw.color();
drawRequest(cursorX, cursorY, block, rotation);
block.drawPlace(cursorX, cursorY, rotation, validPlace(cursorX, cursorY, block, rotation));
}
if(mode == none && !isPlacing()){
BuildRequest req = getRequest(cursorX, cursorY);
if(req != null){
drawSelected(req.x, req.y, req.breaking ? req.tile().block() : req.block, Pal.accent);
}
}
//draw schematic requests
for(BuildRequest request : selectRequests){
request.animScale = 1f;
drawRequest(request);
}
if(sreq != null){
boolean valid = validPlace(sreq.x, sreq.y, sreq.block, sreq.rotation, sreq);
if(sreq.block.rotate){
drawArrow(sreq.block, sreq.x, sreq.y, sreq.rotation, valid);
}
sreq.block.drawRequest(sreq, allRequests(), valid);
drawSelected(sreq.x, sreq.y, sreq.block, getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null ? Pal.remove : Pal.accent);
}
if(Core.input.keyDown(Binding.schematic_select) && !ui.chatfrag.chatOpen()){
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
}
Draw.reset();
}
@@ -142,7 +150,7 @@ public class DesktopInput extends InputHandler{
if(state.is(State.menu) || Core.scene.hasDialog()) return;
//zoom things
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && (Core.input.keyDown(Binding.zoom_hold))){
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && Core.input.keyDown(Binding.zoom_hold)){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
}
@@ -158,6 +166,11 @@ public class DesktopInput extends InputHandler{
mode = none;
}
if(mode != none || isPlacing()){
selectRequests.clear();
lastSchematic = null;
}
if(player.isShooting && !canShoot()){
player.isShooting = false;
}
@@ -169,7 +182,20 @@ public class DesktopInput extends InputHandler{
selectScale = 0f;
}
rotation = Mathf.mod(rotation + (int)Core.input.axisTap(Binding.rotate), 4);
if(!Core.input.keyDown(Binding.zoom_hold) && Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0){
rotation = Mathf.mod(rotation + (int)Core.input.axisTap(Binding.rotate), 4);
if(sreq != null){
sreq.rotation = Mathf.mod(sreq.rotation + (int)Core.input.axisTap(Binding.rotate), 4);
}
if(isPlacing() && mode == placing){
updateLine(selectX, selectY);
}else if(!selectRequests.isEmpty()){
rotateRequests(selectRequests, (int)Core.input.axisTap(Binding.rotate));
}
}
Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY());
@@ -178,7 +204,7 @@ public class DesktopInput extends InputHandler{
cursorType = cursor.block().getCursor(cursor);
if(isPlacing()){
if(isPlacing() || !selectRequests.isEmpty()){
cursorType = SystemCursor.hand;
}
@@ -186,6 +212,10 @@ public class DesktopInput extends InputHandler{
cursorType = ui.drillCursor;
}
if(getRequest(cursor.x, cursor.y) != null && mode == none){
cursorType = SystemCursor.hand;
}
if(canTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y)){
cursorType = ui.unloadCursor;
}
@@ -202,46 +232,173 @@ public class DesktopInput extends InputHandler{
cursorType = SystemCursor.arrow;
}
@Override
public void useSchematic(Schematic schem){
block = null;
schematicX = tileX(getMouseX());
schematicY = tileY(getMouseY());
selectRequests.clear();
selectRequests.addAll(schematics.toRequests(schem, schematicX, schematicY));
mode = none;
}
@Override
public boolean isBreaking(){
return mode == breaking;
}
@Override
public void buildPlacementUI(Table table){
table.addImage().color(Pal.gray).height(4f).colspan(4).growX();
table.row();
table.left().margin(0f).defaults().size(48f).left();
table.addImageButton(Icon.pasteSmall, Styles.clearPartiali, () -> {
ui.schematics.show();
});
}
void pollInput(){
if(scene.getKeyboardFocus() instanceof TextField) return;
Tile selected = tileAt(Core.input.mouseX(), Core.input.mouseY());
int cursorX = tileX(Core.input.mouseX());
int cursorY = tileY(Core.input.mouseY());
int rawCursorX = world.toTile(Core.input.mouseWorld().x), rawCursorY = world.toTile(Core.input.mouseWorld().y);
// automatically pause building if the current build queue is empty
if(Core.settings.getBool("buildautopause") && player.isBuilding && !player.isBuilding()){
player.isBuilding = false;
player.buildWasAutoPaused = true;
}
if(!selectRequests.isEmpty()){
int shiftX = rawCursorX - schematicX, shiftY = rawCursorY - schematicY;
selectRequests.each(s -> {
s.x += shiftX;
s.y += shiftY;
});
schematicX += shiftX;
schematicY += shiftY;
}
if(Core.input.keyTap(Binding.deselect)){
player.setMineTile(null);
}
if(Core.input.keyTap(Binding.clear_building)){
player.clearBuilding();
}
if(Core.input.keyTap(Binding.schematic_select) && !ui.chatfrag.chatOpen()){
schemX = rawCursorX;
schemY = rawCursorY;
}
if(Core.input.keyTap(Binding.schematic_menu) && !ui.chatfrag.chatOpen()){
if(ui.schematics.isShown()){
ui.schematics.hide();
}else{
ui.schematics.show();
}
}
if(Core.input.keyTap(Binding.clear_building)){
lastSchematic = null;
selectRequests.clear();
}
if(Core.input.keyRelease(Binding.schematic_select) && !ui.chatfrag.chatOpen()){
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
useSchematic(lastSchematic);
if(selectRequests.isEmpty()){
lastSchematic = null;
}
}
if(!selectRequests.isEmpty()){
if(Core.input.keyTap(Binding.schematic_flip_x)){
flipRequests(selectRequests, true);
}
if(Core.input.keyTap(Binding.schematic_flip_y)){
flipRequests(selectRequests, false);
}
}
if(sreq != null){
float offset = ((sreq.block.size + 2) % 2) * tilesize / 2f;
float x = Core.input.mouseWorld().x + offset;
float y = Core.input.mouseWorld().y + offset;
sreq.x = (int)(x / tilesize);
sreq.y = (int)(y / tilesize);
}
if(block == null || mode != placing){
lineRequests.clear();
}
if(Core.input.keyTap(Binding.pause_building)){
player.isBuilding = !player.isBuilding;
player.buildWasAutoPaused = false;
}
if((cursorX != lastLineX || cursorY != lastLineY) && isPlacing() && mode == placing){
updateLine(selectX, selectY);
lastLineX = cursorX;
lastLineY = cursorY;
}
if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){
if(isPlacing()){
BuildRequest req = getRequest(cursorX, cursorY);
if(!selectRequests.isEmpty()){
flushRequests(selectRequests);
}else if(isPlacing()){
selectX = cursorX;
selectY = cursorY;
lastLineX = cursorX;
lastLineY = cursorY;
mode = placing;
updateLine(selectX, selectY);
}else if(req != null && !req.breaking && mode == none && !req.initialized){
sreq = req;
}else if(req != null && req.breaking){
deleting = true;
}else if(selected != null){
//only begin shooting if there's no cursor event
if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && player.buildQueue().size == 0 && !droppingItem &&
if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && (player.buildQueue().size == 0 || !player.isBuilding) && !droppingItem &&
!tryBeginMine(selected) && player.getMineTile() == null && !ui.chatfrag.chatOpen()){
player.isShooting = true;
}
}else if(!ui.chatfrag.chatOpen()){ //if it's out of bounds, shooting is just fine
player.isShooting = true;
}
}else if(Core.input.keyTap(Binding.deselect) && (block != null || mode != none || player.isBuilding()) &&
!(player.buildRequest() != null && player.buildRequest().breaking && Core.keybinds.get(Binding.deselect) == Core.keybinds.get(Binding.break_block))){
if(block == null){
player.clearBuilding();
}
}else if(Core.input.keyTap(Binding.deselect) && block != null){
block = null;
mode = none;
}else if(Core.input.keyTap(Binding.break_block) && !Core.scene.hasMouse()){
//is recalculated because setting the mode to breaking removes potential multiblock cursor offset
deleting = false;
mode = breaking;
selectX = tileX(Core.input.mouseX());
selectY = tileY(Core.input.mouseY());
}
if (mode == placing && block != null){
if (!overrideLineRotation && !Core.input.keyDown(Binding.diagonal_placement) && (selectX != cursorX || selectY != cursorY) && ((int) Core.input.axisTap(Binding.rotate) != 0)){
if(Core.input.keyDown(Binding.select) && mode == none && !isPlacing() && deleting){
BuildRequest req = getRequest(cursorX, cursorY);
if(req != null && req.breaking){
player.buildQueue().remove(req);
}
}else{
deleting = false;
}
if(mode == placing && block != null){
if(!overrideLineRotation && !Core.input.keyDown(Binding.diagonal_placement) && (selectX != cursorX || selectY != cursorY) && ((int) Core.input.axisTap(Binding.rotate) != 0)){
rotation = ((int)((Angles.angle(selectX, selectY, cursorX, cursorY) + 45) / 90f)) % 4;
overrideLineRotation = true;
}
@@ -252,29 +409,35 @@ public class DesktopInput extends InputHandler{
if(Core.input.keyRelease(Binding.break_block) || Core.input.keyRelease(Binding.select)){
if(mode == placing && block != null){ //touch up while placing, place everything in selection
iterateLine(selectX, selectY, cursorX, cursorY, l -> {
rotation = l.rotation;
tryPlaceBlock(l.x, l.y);
});
flushRequests(lineRequests);
lineRequests.clear();
Events.fire(new LineConfirmEvent());
}else if(mode == breaking){ //touch up while breaking, break everything in selection
NormalizeResult result = PlaceUtils.normalizeArea(selectX, selectY, cursorX, cursorY, rotation, false, maxLength);
for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){
for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){
int wx = selectX + x * Mathf.sign(cursorX - selectX);
int wy = selectY + y * Mathf.sign(cursorY - selectY);
tryBreakBlock(wx, wy);
}
}
removeSelection(selectX, selectY, cursorX, cursorY);
}
if(selected != null){
tryDropItems(selected.link(), Core.input.mouseWorld().x, Core.input.mouseWorld().y);
}
if(sreq != null){
if(getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null){
player.buildQueue().remove(sreq, true);
}
sreq = null;
}
mode = none;
}
if(Core.input.keyTap(Binding.toggle_power_lines)){
if(Core.settings.getInt("lasersopacity") == 0){
Core.settings.put("lasersopacity", Core.settings.getInt("preferredlaseropacity", 100));
}else{
Core.settings.put("preferredlaseropacity", Core.settings.getInt("lasersopacity"));
Core.settings.put("lasersopacity", 0);
}
}
}
@Override
@@ -298,6 +461,8 @@ public class DesktopInput extends InputHandler{
droppingItem = false;
mode = none;
block = null;
sreq = null;
selectRequests.clear();
}
}
}

View File

@@ -3,13 +3,17 @@ package io.anuke.mindustry.input;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*;
import io.anuke.arc.input.GestureDetector.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
@@ -17,21 +21,30 @@ import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.Placement.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.fragments.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.BuildBlock.*;
import io.anuke.mindustry.world.blocks.power.PowerNode;
import java.util.*;
import static io.anuke.mindustry.Vars.*;
public abstract class InputHandler implements InputProcessor{
public abstract class InputHandler implements InputProcessor, GestureListener{
/** Used for dropping items. */
final static float playerSelectRange = mobile ? 17f : 11f;
/** Maximum line length. */
final static int maxLength = 100;
final static Vector2 stackTrns = new Vector2();
final static Rectangle r1 = new Rectangle(), r2 = new Rectangle();
/** Distance on the back from where items originate. */
final static float backTrns = 3f;
@@ -41,9 +54,15 @@ public abstract class InputHandler implements InputProcessor{
public boolean overrideLineRotation;
public int rotation;
public boolean droppingItem;
public Group uiGroup;
protected PlaceDraw placeDraw = new PlaceDraw();
private PlaceLine line = new PlaceLine();
protected @Nullable Schematic lastSchematic;
protected GestureDetector detector;
protected PlaceLine line = new PlaceLine();
protected BuildRequest resultreq;
protected BuildRequest brequest = new BuildRequest();
protected Array<BuildRequest> lineRequests = new Array<>();
protected Array<BuildRequest> selectRequests = new Array<>();
//methods to override
@@ -92,7 +111,7 @@ public abstract class InputHandler implements InputProcessor{
int[] remaining = {accepted, accepted};
Block block = tile.block();
Events.fire(new DepositEvent());
Core.app.post(() -> Events.fire(new DepositEvent(tile, player, item, accepted)));
for(int i = 0; i < sent; i++){
boolean end = i == sent - 1;
@@ -126,12 +145,22 @@ public abstract class InputHandler implements InputProcessor{
if(tile == null || player == null) return;
if(!Units.canInteract(player, tile)) return;
tile.block().tapped(tile, player);
Core.app.post(() -> Events.fire(new TapEvent(tile, player)));
}
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void onTileConfig(Player player, Tile tile, int value){
if(tile == null || !Units.canInteract(player, tile)) return;
tile.block().configured(tile, player, value);
Core.app.post(() -> Events.fire(new TapConfigEvent(tile, player, value)));
}
public Eachable<BuildRequest> allRequests(){
return cons -> {
for(BuildRequest request : player.buildQueue()) cons.get(request);
for(BuildRequest request : selectRequests) cons.get(request);
for(BuildRequest request : lineRequests) cons.get(request);
};
}
public OverlayFragment getFrag(){
@@ -150,7 +179,11 @@ public abstract class InputHandler implements InputProcessor{
return Core.input.mouseY();
}
public void buildUI(Table table){
public void buildPlacementUI(Table table){
}
public void buildUI(Group group){
}
@@ -158,7 +191,7 @@ public abstract class InputHandler implements InputProcessor{
}
public void drawOutlined(){
public void drawBottom(){
}
@@ -166,8 +199,348 @@ public abstract class InputHandler implements InputProcessor{
}
public boolean isDrawing(){
return false;
public void drawSelected(int x, int y, Block block, Color color){
Draw.color(color);
for(int i = 0; i < 4; i++){
Point2 p = Geometry.d8edge[i];
float offset = -Math.max(block.size - 1, 0) / 2f * tilesize;
Draw.rect("block-select",
x*tilesize + block.offset() + offset * p.x,
y*tilesize + block.offset() + offset * p.y, i * 90);
}
Draw.reset();
}
public void drawBreaking(BuildRequest request){
if(request.breaking){
drawBreaking(request.x, request.y);
}else{
drawSelected(request.x, request.y, request.block, Pal.remove);
}
}
public boolean requestMatches(BuildRequest request){
Tile tile = world.tile(request.x, request.y);
return tile != null && tile.block() instanceof BuildBlock && tile.<BuildEntity>entity().cblock == request.block;
}
public void drawBreaking(int x, int y){
Tile tile = world.ltile(x, y);
if(tile == null) return;
Block block = tile.block();
drawSelected(x, y, block, Pal.remove);
}
public void useSchematic(Schematic schem){
selectRequests.addAll(schematics.toRequests(schem, world.toTile(player.x), world.toTile(player.y)));
}
protected void showSchematicSave(){
if(lastSchematic == null) return;
ui.showTextInput("$schematic.add", "$name", "", text -> {
Schematic replacement = schematics.all().find(s -> s.name().equals(text));
if(replacement != null){
ui.showConfirm("$confirm", "$schematic.replace", () -> {
schematics.overwrite(replacement, lastSchematic);
ui.showInfoFade("$schematic.saved");
ui.schematics.showInfo(replacement);
});
}else{
lastSchematic.tags.put("name", text);
schematics.add(lastSchematic);
ui.showInfoFade("$schematic.saved");
ui.schematics.showInfo(lastSchematic);
}
});
}
public void rotateRequests(Array<BuildRequest> requests, int direction){
int ox = schemOriginX(), oy = schemOriginY();
requests.each(req -> {
//rotate config position
if(req.block.posConfig){
int cx = Pos.x(req.config) - req.originalX, cy = Pos.y(req.config) - req.originalY;
int lx = cx;
if(direction >= 0){
cx = -cy;
cy = lx;
}else{
cx = cy;
cy = -lx;
}
req.config = Pos.get(cx + req.originalX, cy + req.originalY);
}
//rotate actual request, centered on its multiblock position
float wx = (req.x - ox) * tilesize + req.block.offset(), wy = (req.y - oy) * tilesize + req.block.offset();
float x = wx;
if(direction >= 0){
wx = -wy;
wy = x;
}else{
wx = wy;
wy = -x;
}
req.x = world.toTile(wx - req.block.offset()) + ox;
req.y = world.toTile(wy - req.block.offset()) + oy;
req.rotation = Mathf.mod(req.rotation + direction, 4);
});
}
public void flipRequests(Array<BuildRequest> requests, boolean x){
int origin = (x ? schemOriginX() : schemOriginY()) * tilesize;
requests.each(req -> {
float value = -((x ? req.x : req.y) * tilesize - origin + req.block.offset()) + origin;
if(x){
req.x = (int)((value - req.block.offset()) / tilesize);
}else{
req.y = (int)((value - req.block.offset()) / tilesize);
}
if(req.block.posConfig){
int corigin = x ? req.originalWidth/2 : req.originalHeight/2;
int nvalue = -((x ? Pos.x(req.config) : Pos.y(req.config)) - corigin) + corigin;
if(x){
req.originalX = -(req.originalX - corigin) + corigin;
req.config = Pos.get(nvalue, Pos.y(req.config));
}else{
req.originalY = -(req.originalY - corigin) + corigin;
req.config = Pos.get(Pos.x(req.config), nvalue);
}
}
//flip rotation
if(x == (req.rotation % 2 == 0)){
req.rotation = Mathf.mod(req.rotation + 2, 4);
}
});
}
protected int schemOriginX(){
return rawTileX();
}
protected int schemOriginY(){
return rawTileY();
}
/** Returns the selection request that overlaps this position, or null. */
protected BuildRequest getRequest(int x, int y){
return getRequest(x, y, 1, null);
}
/** Returns the selection request that overlaps this position, or null. */
protected BuildRequest getRequest(int x, int y, int size, BuildRequest skip){
float offset = ((size + 1) % 2) * tilesize / 2f;
r2.setSize(tilesize * size);
r2.setCenter(x * tilesize + offset, y * tilesize + offset);
resultreq = null;
Boolf<BuildRequest> test = req -> {
if(req == skip) return false;
Tile other = req.tile();
if(other == null) return false;
if(!req.breaking){
r1.setSize(req.block.size * tilesize);
r1.setCenter(other.worldx() + req.block.offset(), other.worldy() + req.block.offset());
}else{
r1.setSize(other.block().size * tilesize);
r1.setCenter(other.worldx() + other.block().offset(), other.worldy() + other.block().offset());
}
return r2.overlaps(r1);
};
for(BuildRequest req : player.buildQueue()){
if(test.get(req)) return req;
}
for(BuildRequest req : selectRequests){
if(test.get(req)) return req;
}
return null;
}
protected void drawBreakSelection(int x1, int y1, int x2, int y2){
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
NormalizeResult dresult = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
for(int x = dresult.x; x <= dresult.x2; x++){
for(int y = dresult.y; y <= dresult.y2; y++){
Tile tile = world.ltile(x, y);
if(tile == null || !validBreak(tile.x, tile.y)) continue;
drawBreaking(tile.x, tile.y);
}
}
Tmp.r1.set(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
Draw.color(Pal.remove);
Lines.stroke(1f);
for(BuildRequest req : player.buildQueue()){
if(req.breaking) continue;
if(req.bounds(Tmp.r2).overlaps(Tmp.r1)){
drawBreaking(req);
}
}
for(BuildRequest req : selectRequests){
if(req.breaking) continue;
if(req.bounds(Tmp.r2).overlaps(Tmp.r1)){
drawBreaking(req);
}
}
for(BrokenBlock req : state.teams.get(player.getTeam()).brokenBlocks){
Block block = content.block(req.block);
if(block.bounds(req.x, req.y, Tmp.r2).overlaps(Tmp.r1)){
drawSelected(req.x, req.y, content.block(req.block), Pal.remove);
}
}
Lines.stroke(2f);
Draw.color(Pal.removeBack);
Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y);
Draw.color(Pal.remove);
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
}
protected void drawSelection(int x1, int y1, int x2, int y2, int maxLength){
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
Lines.stroke(2f);
Draw.color(Pal.accentBack);
Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y);
Draw.color(Pal.accent);
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
}
protected void flushSelectRequests(Array<BuildRequest> requests){
for(BuildRequest req : requests){
if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){
BuildRequest other = getRequest(req.x, req.y, req.block.size, null);
if(other == null){
selectRequests.add(req.copy());
}else if(!other.breaking && other.x == req.x && other.y == req.y && other.block.size == req.block.size){
selectRequests.remove(other);
selectRequests.add(req.copy());
}
}
}
}
protected void flushRequests(Array<BuildRequest> requests){
for(BuildRequest req : requests){
if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){
BuildRequest copy = req.copy();
if(copy.hasConfig && copy.block.posConfig){
copy.config = Pos.get(Pos.x(copy.config) + copy.x - copy.originalX, Pos.y(copy.config) + copy.y - copy.originalY);
}
player.addBuildRequest(copy);
}
}
}
protected void drawRequest(BuildRequest request){
drawRequest(request.x, request.y, request.block, request.rotation);
}
/** Draws a placement icon for a specific block. */
protected void drawRequest(int x, int y, Block block, int rotation){
brequest.set(x, y, rotation, block);
brequest.animScale = 1f;
block.drawRequest(brequest, allRequests(), validPlace(x, y, block, rotation));
}
/** Remove everything from the queue in a selection. */
protected void removeSelection(int x1, int y1, int x2, int y2){
removeSelection(x1, y1, x2, y2, false);
}
/** Remove everything from the queue in a selection. */
protected void removeSelection(int x1, int y1, int x2, int y2, boolean flush){
NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){
for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){
int wx = x1 + x * Mathf.sign(x2 - x1);
int wy = y1 + y * Mathf.sign(y2 - y1);
Tile tile = world.ltile(wx, wy);
if(tile == null) continue;
if(!flush){
tryBreakBlock(wx, wy);
}else if(validBreak(tile.x, tile.y) && !selectRequests.contains(r -> r.tile() != null && r.tile().link() == tile)){
selectRequests.add(new BuildRequest(tile.x, tile.y));
}
}
}
//remove build requests
Tmp.r1.set(result.x * tilesize, result.y * tilesize, (result.x2 - result.x) * tilesize, (result.y2 - result.y) * tilesize);
Iterator<BuildRequest> it = player.buildQueue().iterator();
while(it.hasNext()){
BuildRequest req = it.next();
if(!req.breaking && req.bounds(Tmp.r2).overlaps(Tmp.r1)){
it.remove();
}
}
it = selectRequests.iterator();
while(it.hasNext()){
BuildRequest req = it.next();
if(!req.breaking && req.bounds(Tmp.r2).overlaps(Tmp.r1)){
it.remove();
}
}
//remove blocks to rebuild
Iterator<BrokenBlock> broken = state.teams.get(player.getTeam()).brokenBlocks.iterator();
while(broken.hasNext()){
BrokenBlock req = broken.next();
Block block = content.block(req.block);
if(block.bounds(req.x, req.y, Tmp.r2).overlaps(Tmp.r1)){
broken.remove();
}
}
}
protected void updateLine(int x1, int y1, int x2, int y2){
lineRequests.clear();
iterateLine(x1, y1, x2, y2, l -> {
rotation = l.rotation;
BuildRequest req = new BuildRequest(l.x, l.y, l.rotation, block);
req.animScale = 1f;
lineRequests.add(req);
});
if(Core.settings.getBool("blockreplace")){
lineRequests.each(req -> {
Block replace = req.block.getReplacement(req, lineRequests);
if(replace.unlockedCur()){
req.block = replace;
}
});
}
}
protected void updateLine(int x1, int y1){
updateLine(x1, y1, tileX(getMouseX()), tileY(getMouseY()));
}
/** Handles tile tap events that are not platform specific. */
@@ -214,13 +587,6 @@ public abstract class InputHandler implements InputProcessor{
}
}
//clear when the player taps on something else
if(!consumed && !mobile && player.isBuilding() && block == null){
player.clearBuilding();
block = null;
return true;
}
if(!showedInventory){
frag.inv.hide();
}
@@ -264,6 +630,14 @@ public abstract class InputHandler implements InputProcessor{
return world.tile(tileX(x), tileY(y));
}
int rawTileX(){
return world.toTile(Core.input.mouseWorld().x);
}
int rawTileY(){
return world.toTile(Core.input.mouseWorld().y);
}
int tileX(float cursorX){
Vector2 vec = Core.input.mouseWorld(cursorX, 0);
if(selectedBlock()){
@@ -288,6 +662,10 @@ public abstract class InputHandler implements InputProcessor{
return block != null;
}
public boolean isBreaking(){
return false;
}
public float mouseAngle(float x, float y){
return Core.input.mouseWorld(getMouseX(), getMouseY()).sub(x, y).angle();
}
@@ -301,16 +679,36 @@ public abstract class InputHandler implements InputProcessor{
table.clear();
}
}
if(detector != null){
Core.input.removeProcessor(detector);
}
if(uiGroup != null){
uiGroup.remove();
uiGroup = null;
}
}
public void add(){
Core.input.addProcessor(detector = new GestureDetector(20, 0.5f, 0.4f, 0.15f, this));
Core.input.addProcessor(this);
if(Core.scene != null){
Table table = (Table)Core.scene.find("inputTable");
if(table != null){
table.clear();
buildUI(table);
buildPlacementUI(table);
}
uiGroup = new WidgetGroup();
uiGroup.touchable(Touchable.childrenOnly);
uiGroup.setFillParent(true);
ui.hudGroup.addChild(uiGroup);
buildUI(uiGroup);
frag.add();
}
if(player != null){
player.isBuilding = true;
}
}
@@ -356,6 +754,18 @@ public abstract class InputHandler implements InputProcessor{
}
public boolean validPlace(int x, int y, Block type, int rotation){
return validPlace(x, y, type, rotation, null);
}
public boolean validPlace(int x, int y, Block type, int rotation, BuildRequest ignore){
for(BuildRequest req : player.buildQueue()){
if(req != ignore
&& !req.breaking
&& req.block.bounds(req.x, req.y, Tmp.r1).overlaps(type.bounds(x, y, Tmp.r2))
&& !(type.canReplace(req.block) && Tmp.r1.equals(Tmp.r2))){
return false;
}
}
return Build.validPlace(player.getTeam(), x, y, type, rotation);
}
@@ -364,6 +774,10 @@ public abstract class InputHandler implements InputProcessor{
}
public void placeBlock(int x, int y, Block block, int rotation){
BuildRequest req = getRequest(x, y);
if(req != null){
player.buildQueue().remove(req);
}
player.addBuildRequest(new BuildRequest(x, y, rotation, block));
}
@@ -392,23 +806,51 @@ public abstract class InputHandler implements InputProcessor{
Core.atlas.find("place-arrow").getHeight() * Draw.scl, rotation * 90 - 90);
}
void iterateLine(int startX, int startY, int endX, int endY, Consumer<PlaceLine> cons){
void iterateLine(int startX, int startY, int endX, int endY, Cons<PlaceLine> cons){
Array<Point2> points;
boolean diagonal = Core.input.keyDown(Binding.diagonal_placement);
if(Core.settings.getBool("swapdiagonal")){
if(Core.settings.getBool("swapdiagonal") && mobile){
diagonal = !diagonal;
}
if(block instanceof PowerNode){
diagonal = !diagonal;
}
if(diagonal){
points = PlaceUtils.normalizeDiagonal(startX, startY, endX, endY);
points = Placement.pathfindLine(block != null && block.conveyorPlacement, startX, startY, endX, endY);
}else{
points = PlaceUtils.normalizeLine(startX, startY, endX, endY);
points = Placement.normalizeLine(startX, startY, endX, endY);
}
if(block instanceof PowerNode){
Array<Point2> skip = new Array<>();
for(int i = 1; i < points.size; i++){
int overlaps = 0;
Point2 point = points.get(i);
//check with how many powernodes the *next* tile will overlap
for(int j = 0; j < i; j++){
if(!skip.contains(points.get(j)) && ((PowerNode)block).overlaps(world.ltile(point.x, point.y), world.ltile(points.get(j).x, points.get(j).y))){
overlaps++;
}
}
//if it's more than one, it can bridge the gap
if(overlaps > 1){
skip.add(points.get(i-1));
}
}
//remove skipped points
points.removeAll(skip);
}
float angle = Angles.angle(startX, startY, endX, endY);
int baseRotation = rotation;
if (!overrideLineRotation || diagonal){
baseRotation = (startX == endX && startY == endY) ? rotation : ((int)((angle + 45) / 90f)) % 4;
if(!overrideLineRotation || diagonal){
baseRotation = (startX == endX && startY == endY) ? rotation : ((int)((angle + 45) / 90f)) % 4;
}
Tmp.r3.set(-1, -1, 0, 0);
@@ -423,25 +865,18 @@ public abstract class InputHandler implements InputProcessor{
Point2 next = i == points.size - 1 ? null : points.get(i + 1);
line.x = point.x;
line.y = point.y;
if (!overrideLineRotation || diagonal){
if(!overrideLineRotation || diagonal){
line.rotation = next != null ? Tile.relativeTo(point.x, point.y, next.x, next.y) : baseRotation;
}else{
line.rotation = rotation;
}
line.last = next == null;
cons.accept(line);
cons.get(line);
Tmp.r3.setSize(block.size * tilesize).setCenter(point.x * tilesize + block.offset(), point.y * tilesize + block.offset());
}
}
public static class PlaceDraw{
public int rotation, scalex, scaley;
public TextureRegion region;
public static final PlaceDraw instance = new PlaceDraw();
}
class PlaceLine{
public int x, y, rotation;
public boolean last;

View File

@@ -2,14 +2,17 @@ package io.anuke.mindustry.input;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.func.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*;
import io.anuke.arc.input.GestureDetector.*;
import io.anuke.arc.input.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.*;
import io.anuke.arc.scene.ui.ImageButton.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.*;
@@ -17,9 +20,9 @@ import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.PlaceUtils.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.*;
@@ -29,17 +32,15 @@ import static io.anuke.mindustry.input.PlaceMode.*;
public class MobileInput extends InputHandler implements GestureListener{
/** Maximum speed the player can pan. */
private static final float maxPanSpeed = 1.3f;
private static Rectangle r1 = new Rectangle(), r2 = new Rectangle();
/** Distance to edge of screen to start panning. */
private final float edgePan = Scl.scl(60f);
//gesture data
private Vector2 vector = new Vector2();
private float lastZoom = -1;
private GestureDetector detector;
/** Position where the player started dragging a line. */
private int lineStartX, lineStartY;
private int lineStartX, lineStartY, lastLineX, lastLineY;
/** Animation scale for line. */
private float lineScale;
@@ -49,22 +50,20 @@ public class MobileInput extends InputHandler implements GestureListener{
/** Used for shifting build requests. */
private float shiftDeltaX, shiftDeltaY;
/** List of currently selected tiles to place. */
private Array<PlaceRequest> selection = new Array<>();
/** Place requests to be removed. */
private Array<PlaceRequest> removals = new Array<>();
private Array<BuildRequest> removals = new Array<>();
/** Whether or not the player is currently shifting all placed tiles. */
private boolean selecting;
/** Whether the player is currently in line-place mode. */
private boolean lineMode;
private boolean lineMode, schematicMode;
/** Current place mode. */
private PlaceMode mode = none;
/** Whether no recipe was available when switching to break mode. */
private Block lastBlock;
/** Last placed request. Used for drawing block overlay. */
private PlaceRequest lastPlaced;
private int prevX, prevY, prevRotation;
private BuildRequest lastPlaced;
/** Down tracking for panning.*/
private boolean down = false;
//region utility methods
@@ -99,10 +98,10 @@ public class MobileInput extends InputHandler implements GestureListener{
r2.setSize(block.size * tilesize);
r2.setCenter(x * tilesize + block.offset(), y * tilesize + block.offset());
for(PlaceRequest req : selection){
for(BuildRequest req : selectRequests){
Tile other = req.tile();
if(other == null || req.remove) continue;
if(other == null || req.breaking) continue;
r1.setSize(req.block.size * tilesize);
r1.setCenter(other.worldx() + req.block.offset(), other.worldy() + req.block.offset());
@@ -128,16 +127,16 @@ public class MobileInput extends InputHandler implements GestureListener{
}
/** Returns the selection request that overlaps this tile, or null. */
PlaceRequest getRequest(Tile tile){
BuildRequest getRequest(Tile tile){
r2.setSize(tilesize);
r2.setCenter(tile.worldx(), tile.worldy());
for(PlaceRequest req : selection){
for(BuildRequest req : selectRequests){
Tile other = req.tile();
if(other == null) continue;
if(!req.remove){
if(!req.breaking){
r1.setSize(req.block.size * tilesize);
r1.setCenter(other.worldx() + req.block.offset(), other.worldy() + req.block.offset());
@@ -156,9 +155,11 @@ public class MobileInput extends InputHandler implements GestureListener{
return null;
}
void removeRequest(PlaceRequest request){
selection.removeValue(request, true);
removals.add(request);
void removeRequest(BuildRequest request){
selectRequests.removeValue(request, true);
if(!request.breaking){
removals.add(request);
}
}
boolean isLinePlacing(){
@@ -172,80 +173,8 @@ public class MobileInput extends InputHandler implements GestureListener{
//endregion
//region UI and drawing
void drawRequest(PlaceRequest request, PlaceRequest prev){
Tile tile = request.tile();
if(!request.remove){
if(prev != null){
request.block.getPlaceDraw(placeDraw, request.rotation, prev.x - request.x, prev.y - request.y, prev.rotation);
}else{
request.block.getPlaceDraw(placeDraw, request.rotation, 0, 0, request.rotation);
}
//draw placing request
float offset = request.block.offset();
TextureRegion region = placeDraw.region;
Draw.mixcol(Pal.accent, Mathf.clamp((1f - request.scale) / 0.5f + 0.12f + Mathf.absin(Time.time(), 8f, 0.35f)));
Draw.tint(Color.white, Pal.breakInvalid, request.redness);
Draw.rect(region, tile.worldx() + offset, tile.worldy() + offset,
region.getWidth() * request.scale * Draw.scl * placeDraw.scalex,
region.getHeight() * request.scale * Draw.scl * placeDraw.scaley,
request.block.rotate ? placeDraw.rotation * 90 : 0);
Draw.mixcol(Pal.accent, 1f);
for(int i = 0; i < 4; i++){
Point2 p = Geometry.d8edge[i];
float poffset = -Math.max(request.block.size - 1, 0) / 2f * tilesize;
TextureRegion find = Core.atlas.find("block-select");
if(i % 2 == 0)
Draw.rect("block-select", request.tile().x * tilesize + request.block.offset() + poffset * p.x, request.tile().y * tilesize + request.block.offset() + poffset * p.y,
find.getWidth() * Draw.scl * request.scale, find.getHeight() * Draw.scl * request.scale, i * 90);
}
Draw.color();
}else{
float rad = Math.max((tile.block().size * tilesize / 2f - 1) * request.scale, 1f);
if(rad <= 1.01f) return;
Draw.mixcol();
//draw removing request
Draw.tint(Pal.removeBack);
Lines.square(tile.drawx(), tile.drawy() - 1, rad);
Draw.tint(Pal.remove);
Lines.square(tile.drawx(), tile.drawy(), rad);
}
}
/** Draws a placement icon for a specific block. */
void drawPlace(int x, int y, Block block, int rotation, int prevX, int prevY, int prevRotation){
if(validPlace(x, y, block, rotation) && !checkOverlapPlacement(x, y, block)){
block.getPlaceDraw(placeDraw, rotation, prevX, prevY, prevRotation);
Draw.color();
Draw.rect(placeDraw.region, x * tilesize + block.offset(), y * tilesize + block.offset(),
placeDraw.region.getWidth() * Draw.scl * placeDraw.scalex,
placeDraw.region.getHeight() * Draw.scl * placeDraw.scaley,
block.rotate ? placeDraw.rotation * 90 : 0);
Draw.color(Pal.accent);
for(int i = 0; i < 4; i++){
Point2 p = Geometry.d8edge[i];
float offset = -Math.max(block.size - 1, 0) / 2f * tilesize;
if(i % 2 == 0)
Draw.rect("block-select", x * tilesize + block.offset() + offset * p.x, y * tilesize + block.offset() + offset * p.y, i * 90);
}
Draw.color();
}else{
Draw.color(Pal.removeBack);
Lines.square(x * tilesize + block.offset(), y * tilesize + block.offset() - 1, block.size * tilesize / 2f - 1);
Draw.color(Pal.remove);
Lines.square(x * tilesize + block.offset(), y * tilesize + block.offset(), block.size * tilesize / 2f - 1);
}
}
@Override
public void buildUI(Table table){
public void buildPlacementUI(Table table){
table.addImage().color(Pal.gray).height(4f).colspan(4).growX();
table.row();
table.left().margin(0f).defaults().size(48f);
@@ -262,22 +191,49 @@ public class MobileInput extends InputHandler implements GestureListener{
}).update(l -> l.setChecked(Core.settings.getBool("swapdiagonal")));
//rotate button
table.addImageButton(Icon.arrowSmall, Styles.clearPartiali,() -> rotation = Mathf.mod(rotation + 1, 4))
.update(i -> i.getImage().setRotationOrigin(rotation * 90, Align.center)).visible(() -> block != null && block.rotate);
table.addImageButton(Icon.arrowSmall, Styles.clearTogglePartiali, () -> {
if(block != null && block.rotate){
rotation = Mathf.mod(rotation + 1, 4);
}else{
schematicMode = !schematicMode;
if(schematicMode){
block = null;
mode = none;
}
}
}).update(i -> {
boolean arrow = block != null && block.rotate;
i.getImage().setRotationOrigin(!arrow ? 0 : rotation * 90, Align.center);
i.getStyle().imageUp = arrow ? Icon.arrowSmall : Icon.pasteSmall;
i.setChecked(!arrow && schematicMode);
});
//confirm button
table.addImageButton(Icon.checkSmall, Styles.clearPartiali, () -> {
for(PlaceRequest request : selection){
for(BuildRequest request : selectRequests){
Tile tile = request.tile();
//actually place/break all selected blocks
if(tile != null){
if(!request.remove){
if(!request.breaking){
if(validPlace(request.x, request.y, request.block, request.rotation)){
BuildRequest other = getRequest(request.x, request.y, request.block.size, null);
BuildRequest copy = request.copy();
if(copy.hasConfig && copy.block.posConfig){
copy.config = Pos.get(Pos.x(copy.config) + copy.x - copy.originalX, Pos.y(copy.config) + copy.y - copy.originalY);
}
if(other == null){
player.addBuildRequest(copy);
}else if(!other.breaking && other.x == request.x && other.y == request.y && other.block.size == request.block.size){
player.buildQueue().remove(other);
player.addBuildRequest(copy);
}
}
rotation = request.rotation;
Block before = block;
block = request.block;
tryPlaceBlock(tile.x, tile.y);
block = before;
}else{
tryBreakBlock(tile.x, tile.y);
}
@@ -285,25 +241,60 @@ public class MobileInput extends InputHandler implements GestureListener{
}
//move all current requests to removal array so they fade out
removals.addAll(selection);
selection.clear();
removals.addAll(selectRequests.select(r -> !r.breaking));
selectRequests.clear();
selecting = false;
}).visible(() -> !selection.isEmpty()).name("confirmplace");
}).visible(() -> !selectRequests.isEmpty()).name("confirmplace");
}
Core.scene.table(t -> {
t.setName("cancelMobile");
t.bottom().left().visible(() -> (player.isBuilding() || block != null || mode == breaking) && !state.is(State.menu));
t.addImageTextButton("$cancel", Icon.cancelSmall, () -> {
player.clearBuilding();
mode = none;
block = null;
}).width(155f);
@Override
public void buildUI(Group group){
Boolp schem = () -> lastSchematic != null && !selectRequests.isEmpty();
group.fill(t -> {
t.bottom().left().visible(() -> (player.isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get());
t.addImageTextButton("$cancel", Icon.cancelSmall, () -> {
player.clearBuilding();
selectRequests.clear();
mode = none;
block = null;
}).width(155f);
});
group.fill(t -> {
t.visible(schem);
t.bottom().left();
t.table(Tex.pane, b -> {
b.defaults().size(50f);
ImageButtonStyle style = Styles.clearPartiali;
b.addImageButton(Icon.floppySmall, style, this::showSchematicSave).disabled(f -> lastSchematic == null || lastSchematic.file != null);
b.addImageButton(Icon.cancelSmall, style, () -> {
selectRequests.clear();
});
b.row();
b.addImageButton(Icon.flipSmall, style, () -> flipRequests(selectRequests, true));
b.addImageButton(Icon.flipSmall, style, () -> flipRequests(selectRequests, false)).update(i -> i.getImage().setRotationOrigin(90f, Align.center));
b.row();
b.addImageButton(Icon.rotateSmall, style, () -> rotateRequests(selectRequests, 1));
}).margin(4f);
});
}
@Override
public boolean isDrawing(){
return selection.size > 0 || removals.size > 0 || lineMode || player.target != null || mode != PlaceMode.none;
protected int schemOriginX(){
Tmp.v1.setZero();
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
return world.toTile(Tmp.v1.scl(1f / selectRequests.size).x);
}
@Override
protected int schemOriginY(){
Tmp.v1.setZero();
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
return world.toTile(Tmp.v1.scl(1f / selectRequests.size).y);
}
@Override
@@ -312,55 +303,53 @@ public class MobileInput extends InputHandler implements GestureListener{
}
@Override
public void drawOutlined(){
public void drawBottom(){
Lines.stroke(1f);
//draw removals
for(PlaceRequest request : removals){
for(BuildRequest request : removals){
Tile tile = request.tile();
if(tile == null) continue;
request.scale = Mathf.lerpDelta(request.scale, 0f, 0.2f);
request.redness = Mathf.lerpDelta(request.redness, 0f, 0.2f);
request.animScale = Mathf.lerpDelta(request.animScale, 0f, 0.2f);
drawRequest(request, null);
if(request.breaking){
drawSelected(request.x, request.y, tile.block(), Pal.remove);
}else{
request.block.drawRequest(request, allRequests(), true);
}
}
PlaceRequest last = null;
//draw list of requests
for(PlaceRequest request : selection){
for(BuildRequest request : selectRequests){
Tile tile = request.tile();
if(tile == null) continue;
if((!request.remove && validPlace(tile.x, tile.y, request.block, request.rotation))
|| (request.remove && validBreak(tile.x, tile.y))){
request.scale = Mathf.lerpDelta(request.scale, 1f, 0.2f);
request.redness = Mathf.lerpDelta(request.redness, 0f, 0.2f);
if((!request.breaking && validPlace(tile.x, tile.y, request.block, request.rotation))
|| (request.breaking && validBreak(tile.x, tile.y))){
request.animScale = Mathf.lerpDelta(request.animScale, 1f, 0.2f);
}else{
request.scale = Mathf.lerpDelta(request.scale, 0.6f, 0.1f);
request.redness = Mathf.lerpDelta(request.redness, 0.9f, 0.2f);
request.animScale = Mathf.lerpDelta(request.animScale, 0.6f, 0.1f);
}
Tmp.c1.set(Draw.getMixColor());
if(!request.remove && request == lastPlaced && request.block != null){
if(!request.breaking && request == lastPlaced && request.block != null){
Draw.mixcol();
if(request.block.rotate) drawArrow(request.block, tile.x, tile.y, request.rotation);
}
Draw.mixcol(Tmp.c1, 1f);
drawRequest(request, last);
//Draw.mixcol(Tmp.c1, 1f);
Draw.reset();
drawRequest(request);
//draw last placed request
if(!request.remove && request == lastPlaced && request.block != null){
if(!request.breaking && request == lastPlaced && request.block != null){
Draw.mixcol();
request.block.drawPlace(tile.x, tile.y, rotation, validPlace(tile.x, tile.y, request.block, rotation));
}
last = request;
}
Draw.mixcol();
@@ -373,46 +362,19 @@ public class MobileInput extends InputHandler implements GestureListener{
if(mode == placing && block != null){
//draw placing
prevX = lineStartX;
prevY = lineStartY;
prevRotation = rotation;
iterateLine(lineStartX, lineStartY, tileX, tileY, l -> {
if(l.last && block.rotate){
drawArrow(block, l.x, l.y, l.rotation);
for(int i = 0; i < lineRequests.size; i++){
BuildRequest req = lineRequests.get(i);
if(i == lineRequests.size - 1 && req.block.rotate){
drawArrow(block, req.x, req.y, req.rotation);
}
drawPlace(l.x, l.y, block, l.rotation, prevX - l.x, prevY - l.y, prevRotation);
rotation = l.rotation;
prevX = l.x;
prevY = l.y;
prevRotation = l.rotation;
});
}else if(mode == breaking){
//draw breaking
NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, lineStartX, lineStartY, tileX, tileY, false, maxLength, 1f);
NormalizeResult dresult = PlaceUtils.normalizeArea(lineStartX, lineStartY, tileX, tileY, rotation, false, maxLength);
for(int x = dresult.x; x <= dresult.x2; x++){
for(int y = dresult.y; y <= dresult.y2; y++){
Tile other = world.ltile(x, y);
if(other == null || !validBreak(other.x, other.y)) continue;
Draw.color(Pal.removeBack);
Lines.square(other.drawx(), other.drawy() - 1, other.block().size * tilesize / 2f - 1);
Draw.color(Pal.remove);
Lines.square(other.drawx(), other.drawy(), other.block().size * tilesize / 2f - 1);
}
BuildRequest request = lineRequests.get(i);
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation) && getRequest(req.x, request.y, request.block.size, null) == null);
drawSelected(request.x, request.y, request.block, Pal.accent);
}
Draw.color(Pal.removeBack);
Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y);
Draw.color(Pal.remove);
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
}else if(mode == breaking){
drawBreakSelection(lineStartX, lineStartY, tileX, tileY);
}
}
TargetTrait target = player.target;
@@ -438,31 +400,49 @@ public class MobileInput extends InputHandler implements GestureListener{
Draw.reset();
}
@Override
public void drawTop(){
//draw schematic selection
if(mode == schematicSelect){
drawSelection(lineStartX, lineStartY, lastLineX, lastLineY, Vars.maxSchematicSize);
}
}
@Override
protected void drawRequest(BuildRequest request){
if(request.tile() == null) return;
brequest.animScale = request.animScale = Mathf.lerpDelta(request.animScale, 1f, 0.1f);
if(request.breaking){
drawSelected(request.x, request.y, request.tile().block(), Pal.remove);
}else{
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation));
drawSelected(request.x, request.y, request.block, Pal.accent);
}
}
//endregion
//region input events
@Override
public void add(){
Core.input.addProcessor(detector = new GestureDetector(20, 0.5f, 0.4f, 0.15f, this));
super.add();
public boolean isBreaking(){
return mode == breaking;
}
@Override
public void remove(){
super.remove();
if(detector != null){
Core.input.removeProcessor(detector);
}
if(Core.scene != null && Core.scene.find("cancelMobile") != null){
Core.scene.find("cancelMobile").remove();
}
public void useSchematic(Schematic schem){
selectRequests.clear();
selectRequests.addAll(schematics.toRequests(schem, world.toTile(player.x), world.toTile(player.y)));
lastSchematic = schem;
}
@Override
public boolean touchDown(int screenX, int screenY, int pointer, KeyCode button){
if(state.is(State.menu) || player.isDead()) return false;
down = true;
//get tile on cursor
Tile cursor = tileAt(screenX, screenY);
@@ -472,11 +452,20 @@ public class MobileInput extends InputHandler implements GestureListener{
if(cursor == null || Core.scene.hasMouse(screenX, screenY)) return false;
//only begin selecting if the tapped block is a request
selecting = hasRequest(cursor) && isPlacing() && mode == placing;
selecting = hasRequest(cursor);
//call tap events
if(pointer == 0 && !selecting){
if(!tryTapPlayer(worldx, worldy) && Core.settings.getBool("keyboard")){
if(schematicMode && block == null){
mode = schematicSelect;
//engage schematic selection mode
int tileX = tileX(screenX);
int tileY = tileY(screenY);
lineStartX = tileX;
lineStartY = tileY;
lastLineX = tileX;
lastLineY = tileY;
}else if(!tryTapPlayer(worldx, worldy) && Core.settings.getBool("keyboard")){
//shoot on touch down when in keyboard mode
player.isShooting = true;
}
@@ -489,47 +478,34 @@ public class MobileInput extends InputHandler implements GestureListener{
public boolean touchUp(int screenX, int screenY, int pointer, KeyCode button){
lastZoom = renderer.getScale();
if(!Core.input.isTouched()){
down = false;
}
selecting = false;
//place down a line if in line mode
if(lineMode){
int tileX = tileX(screenX);
int tileY = tileY(screenY);
if(mode == placing && isPlacing()){
iterateLine(lineStartX, lineStartY, tileX, tileY, l -> {
Tile tile = world.tile(l.x, l.y);
if(tile != null && checkOverlapPlacement(tile.x, tile.y, block)){
return;
}
PlaceRequest request = new PlaceRequest(l.x, l.y, block, l.rotation);
request.scale = 1f;
selection.add(request);
});
flushSelectRequests(lineRequests);
Events.fire(new LineConfirmEvent());
}else if(mode == breaking){
//normalize area
NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tileX, tileY, rotation, false, maxLength);
//break everything in area
for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){
for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){
int wx = lineStartX + x * Mathf.sign(tileX - lineStartX);
int wy = lineStartY + y * Mathf.sign(tileY - lineStartY);
Tile tar = world.ltile(wx, wy);
if(tar == null) continue;
if(!hasRequest(world.tile(tar.x, tar.y)) && validBreak(tar.x, tar.y)){
PlaceRequest request = new PlaceRequest(tar.x, tar.y);
request.scale = 1f;
selection.add(request);
}
}
}
removeSelection(lineStartX, lineStartY, tileX, tileY, true);
}
lineMode = false;
}else if(mode == schematicSelect){
selectRequests.clear();
lastSchematic = schematics.create(lineStartX, lineStartY, lastLineX, lastLineY);
useSchematic(lastSchematic);
if(selectRequests.isEmpty()){
lastSchematic = null;
}
schematicMode = false;
mode = none;
}else{
Tile tile = tileAt(screenX, screenY);
@@ -548,17 +524,20 @@ public class MobileInput extends InputHandler implements GestureListener{
Tile cursor = tileAt(x, y);
//ignore off-screen taps
if(cursor == null || Core.scene.hasMouse(x, y)) return false;
if(cursor == null || Core.scene.hasMouse(x, y) || schematicMode) return false;
//remove request if it's there
//long pressing enables line mode otherwise
lineStartX = cursor.x;
lineStartY = cursor.y;
lastLineX = cursor.x;
lastLineY = cursor.y;
lineMode = true;
if(mode == breaking){
Effects.effect(Fx.tapBlock, cursor.worldx(), cursor.worldy(), 1f);
}else if(block != null){
updateLine(lineStartX, lineStartY, cursor.x, cursor.y);
Effects.effect(Fx.tapBlock, cursor.worldx() + block.offset(), cursor.worldy() + block.offset(), block.size);
}
@@ -584,11 +563,11 @@ public class MobileInput extends InputHandler implements GestureListener{
removeRequest(getRequest(cursor));
}else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, block, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, block)){
//add to selection queue if it's a valid place position
selection.add(lastPlaced = new PlaceRequest(cursor.x, cursor.y, block, rotation));
selectRequests.add(lastPlaced = new BuildRequest(cursor.x, cursor.y, rotation, block));
}else if(mode == breaking && validBreak(cursor.link().x, cursor.link().y) && !hasRequest(cursor.link())){
//add to selection queue if it's a valid BREAK position
cursor = cursor.link();
selection.add(new PlaceRequest(cursor.x, cursor.y));
selectRequests.add(new BuildRequest(cursor.x, cursor.y));
}else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.link())){
tryBeginMine(cursor);
}
@@ -598,12 +577,16 @@ public class MobileInput extends InputHandler implements GestureListener{
@Override
public void update(){
if(state.is(State.menu) || player.isDead()){
selection.clear();
if(state.is(State.menu) ){
selectRequests.clear();
removals.clear();
mode = none;
}
if(player.isDead()){
mode = none;
}
//zoom things
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && (Core.input.keyDown(Binding.zoom_hold))){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
@@ -627,10 +610,7 @@ public class MobileInput extends InputHandler implements GestureListener{
//reset state when not placing
if(mode == none){
selecting = false;
lineMode = false;
removals.addAll(selection);
selection.clear();
}
if(lineMode && mode == placing && block == null){
@@ -646,6 +626,22 @@ public class MobileInput extends InputHandler implements GestureListener{
mode = none;
}
//stop schematic when in block mode
if(block != null){
schematicMode = false;
}
//stop select when not in schematic mode
if(!schematicMode && mode == schematicSelect){
mode = none;
}
if(mode == schematicSelect){
lastLineX = rawTileX();
lastLineY = rawTileY();
autoPan();
}
//automatically switch to placing after a new recipe is selected
if(lastBlock != block && mode == breaking && block != null){
mode = placing;
@@ -656,49 +652,62 @@ public class MobileInput extends InputHandler implements GestureListener{
lineScale = Mathf.lerpDelta(lineScale, 1f, 0.1f);
//When in line mode, pan when near screen edges automatically
if(Core.input.isTouched(0) && lineMode){
float screenX = Core.input.mouseX(), screenY = Core.input.mouseY();
if(Core.input.isTouched(0)){
autoPan();
}
float panX = 0, panY = 0;
int lx = tileX(Core.input.mouseX()), ly = tileY(Core.input.mouseY());
if(screenX <= edgePan){
panX = -(edgePan - screenX);
}
if(screenX >= Core.graphics.getWidth() - edgePan){
panX = (screenX - Core.graphics.getWidth()) + edgePan;
}
if(screenY <= edgePan){
panY = -(edgePan - screenY);
}
if(screenY >= Core.graphics.getHeight() - edgePan){
panY = (screenY - Core.graphics.getHeight()) + edgePan;
}
vector.set(panX, panY).scl((Core.camera.width) / Core.graphics.getWidth());
vector.limit(maxPanSpeed);
//pan view
Core.camera.position.x += vector.x;
Core.camera.position.y += vector.y;
if((lastLineX != lx || lastLineY != ly) && isPlacing()){
lastLineX = lx;
lastLineY = ly;
updateLine(lineStartX, lineStartY, lx, ly);
}
}else{
lineRequests.clear();
lineScale = 0f;
}
//remove place requests that have disappeared
for(int i = removals.size - 1; i >= 0; i--){
PlaceRequest request = removals.get(i);
BuildRequest request = removals.get(i);
if(request.scale <= 0.0001f){
if(request.animScale <= 0.0001f){
removals.remove(i);
i--;
}
}
}
protected void autoPan(){
float screenX = Core.input.mouseX(), screenY = Core.input.mouseY();
float panX = 0, panY = 0;
if(screenX <= edgePan){
panX = -(edgePan - screenX);
}
if(screenX >= Core.graphics.getWidth() - edgePan){
panX = (screenX - Core.graphics.getWidth()) + edgePan;
}
if(screenY <= edgePan){
panY = -(edgePan - screenY);
}
if(screenY >= Core.graphics.getHeight() - edgePan){
panY = (screenY - Core.graphics.getHeight()) + edgePan;
}
vector.set(panX, panY).scl((Core.camera.width) / Core.graphics.getWidth());
vector.limit(maxPanSpeed);
//pan view
Core.camera.position.x += vector.x;
Core.camera.position.y += vector.y;
}
@Override
public boolean pan(float x, float y, float deltaX, float deltaY){
if(Core.scene.hasDialog() || Core.settings.getBool("keyboard")) return false;
@@ -708,10 +717,12 @@ public class MobileInput extends InputHandler implements GestureListener{
deltaY *= scale;
//can't pan in line mode with one finger or while dropping items!
if((lineMode && !Core.input.isTouched(1)) || droppingItem){
if((lineMode && !Core.input.isTouched(1)) || droppingItem || schematicMode){
return false;
}
if(!down) return false;
if(selecting){ //pan all requests
shiftDeltaX += deltaX;
shiftDeltaY += deltaY;
@@ -720,8 +731,8 @@ public class MobileInput extends InputHandler implements GestureListener{
int shiftedY = (int)(shiftDeltaY / tilesize);
if(Math.abs(shiftedX) > 0 || Math.abs(shiftedY) > 0){
for(PlaceRequest req : selection){
if(req.remove) continue; //don't shift removal requests
for(BuildRequest req : selectRequests){
if(req.breaking) continue; //don't shift removal requests
req.x += shiftedX;
req.y += shiftedY;
}
@@ -756,33 +767,4 @@ public class MobileInput extends InputHandler implements GestureListener{
}
//endregion
private class PlaceRequest{
int x, y;
Block block;
int rotation;
boolean remove;
//animation variables
float scale;
float redness;
PlaceRequest(int x, int y, Block block, int rotation){
this.x = x;
this.y = y;
this.block = block;
this.rotation = rotation;
this.remove = false;
}
PlaceRequest(int x, int y){
this.x = x;
this.y = y;
this.remove = true;
}
Tile tile(){
return world.tile(x, y);
}
}
}

View File

@@ -1,5 +1,5 @@
package io.anuke.mindustry.input;
enum PlaceMode{
none, breaking, placing
none, breaking, placing, schematicSelect
}

View File

@@ -1,25 +1,41 @@
package io.anuke.mindustry.input;
import io.anuke.arc.collection.Array;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Bresenham2;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.world.Block;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.tilesize;
import java.util.*;
public class PlaceUtils{
import static io.anuke.mindustry.Vars.*;
public class Placement{
private static final NormalizeResult result = new NormalizeResult();
private static final NormalizeDrawResult drawResult = new NormalizeDrawResult();
private static Bresenham2 bres = new Bresenham2();
private static Array<Point2> points = new Array<>();
//for pathfinding
private static IntFloatMap costs = new IntFloatMap();
private static IntIntMap parents = new IntIntMap();
private static IntSet closed = new IntSet();
/** Normalize a diagonal line into points. */
public static Array<Point2> normalizeDiagonal(int startX, int startY, int endX, int endY){
public static Array<Point2> pathfindLine(boolean conveyors, int startX, int startY, int endX, int endY){
Pools.freeAll(points);
points.clear();
return bres.lineNoDiagonal(startX, startY, endX, endY, Pools.get(Point2.class, Point2::new), points);
if(conveyors && Core.settings.getBool("conveyorpathfinding")){
if(astar(startX, startY, endX, endY)){
return points;
}else{
return normalizeLine(startX, startY, endX, endY);
}
}else{
return bres.lineNoDiagonal(startX, startY, endX, endY, Pools.get(Point2.class, Point2::new), points);
}
}
/** Normalize two points into one straight line, no diagonals. */
@@ -40,6 +56,92 @@ public class PlaceUtils{
return points;
}
private static float tileHeuristic(Tile tile, Tile other){
Block block = control.input.block;
if((!other.block().alwaysReplace && !(block != null && block.canReplace(other.block()))) || other.floor().isDeep()){
return 20;
}else{
if(parents.containsKey(tile.pos())){
Tile prev = world.tile(parents.get(tile.pos(), 0));
if(tile.relativeTo(prev) != other.relativeTo(tile)){
return 8;
}
}
}
return 1;
}
private static float distanceHeuristic(int x1, int y1, int x2, int y2){
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
private static boolean validNode(Tile tile, Tile other){
Block block = control.input.block;
if(block != null && block.canReplace(other.block())){
return true;
}else{
return other.block().alwaysReplace;
}
}
private static boolean astar(int startX, int startY, int endX, int endY){
Tile start = world.tile(startX, startY);
Tile end = world.tile(endX, endY);
if(start == end || start == null || end == null) return false;
costs.clear();
closed.clear();
parents.clear();
int nodeLimit = 1000;
int totalNodes = 0;
PriorityQueue<Tile> queue = new PriorityQueue<>(10, (a, b) -> Float.compare(costs.get(a.pos(), 0f) + distanceHeuristic(a.x, a.y, end.x, end.y), costs.get(b.pos(), 0f) + distanceHeuristic(b.x, b.y, end.x, end.y)));
queue.add(start);
boolean found = false;
while(!queue.isEmpty() && totalNodes++ < nodeLimit){
Tile next = queue.poll();
float baseCost = costs.get(next.pos(), 0f);
if(next == end){
found = true;
break;
}
closed.add(Pos.get(next.x, next.y));
for(Point2 point : Geometry.d4){
int newx = next.x + point.x, newy = next.y + point.y;
Tile child = world.tile(newx, newy);
if(child != null && validNode(next, child)){
if(closed.add(child.pos())){
parents.put(child.pos(), next.pos());
costs.put(child.pos(), tileHeuristic(next, child) + baseCost);
queue.add(child);
}
}
}
}
if(!found) return false;
int total = 0;
points.add(Pools.obtain(Point2.class, Point2::new).set(endX, endY));
Tile current = end;
while(current != start && total++ < nodeLimit){
if(current == null) return false;
int newPos = parents.get(current.pos(), Pos.invalid);
if(newPos == Pos.invalid) return false;
points.add(Pools.obtain(Point2.class, Point2::new).set(Pos.x(newPos), Pos.y(newPos)));
current = world.tile(newPos);
}
points.reverse();
return true;
}
/**
* Normalizes a placement area and returns the result, ready to be used for drawing a rectangle.
* Returned x2 and y2 will <i>always</i> be greater than x and y.
@@ -95,14 +197,14 @@ public class PlaceUtils{
}else{
endx = tilex;
}
}
if(Math.abs(endx - tilex) > maxLength){
endx = Mathf.sign(endx - tilex) * maxLength + tilex;
}
if(Math.abs(endx - tilex) > maxLength){
endx = Mathf.sign(endx - tilex) * maxLength + tilex;
}
if(Math.abs(endy - tiley) > maxLength){
endy = Mathf.sign(endy - tiley) * maxLength + tiley;
}
if(Math.abs(endy - tiley) > maxLength){
endy = Mathf.sign(endy - tiley) * maxLength + tiley;
}
int dx = endx - tilex, dy = endy - tiley;
@@ -141,12 +243,12 @@ public class PlaceUtils{
return result;
}
static class NormalizeDrawResult{
public static class NormalizeDrawResult{
float x, y, x2, y2;
}
static class NormalizeResult{
int x, y, x2, y2, rotation;
public static class NormalizeResult{
public int x, y, x2, y2, rotation;
boolean isX(){
return Math.abs(x2 - x) > Math.abs(y2 - y);
@@ -173,4 +275,12 @@ public class PlaceUtils{
return y + (x2 - x > y2 - y ? 0 : i);
}
}
public interface DistanceHeuristic{
float cost(int x1, int y1, int x2, int y2);
}
public interface TileHueristic{
float cost(Tile tile, Tile other);
}
}

View File

@@ -4,16 +4,32 @@ import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Json.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.MappableContent;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import java.io.*;
@SuppressWarnings("unchecked")
public class JsonIO{
private static CustomJson jsonBase = new CustomJson();
private static Json json = new Json(){{
apply(this);
}};
private static Json json = new Json(){
{ apply(this); }
@Override
public void writeValue(Object value, Class knownType, Class elementType){
if(value instanceof io.anuke.mindustry.ctype.MappableContent){
try{
getWriter().value(((MappableContent)value).name);
}catch(IOException e){
throw new RuntimeException(e);
}
}else{
super.writeValue(value, knownType, elementType);
}
}
};
public static String write(Object object){
return json.toJson(object, object.getClass());
@@ -66,24 +82,6 @@ public class JsonIO{
}
});
//TODO extremely hacky and disgusting
for(Block block : Vars.content.blocks()){
Class type = block.getClass();
if(type.isAnonymousClass()) type = type.getSuperclass();
json.setSerializer(type, new Serializer<Block>(){
@Override
public void write(Json json, Block object, Class knownType){
json.writeValue(object.name);
}
@Override
public Block read(Json json, JsonValue jsonData, Class type){
return Vars.content.getByName(ContentType.block, jsonData.asString());
}
});
}
json.setSerializer(Block.class, new Serializer<Block>(){
@Override
public void write(Json json, Block object, Class knownType){
@@ -96,26 +94,6 @@ public class JsonIO{
}
});
/*
json.setSerializer(TeamData.class, new Serializer<TeamData>(){
@Override
public void write(Json json, TeamData object, Class knownType){
json.writeObjectStart();
json.writeValue("brokenBlocks", object.brokenBlocks.toArray());
json.writeValue("team", object.team.ordinal());
json.writeObjectEnd();
}
@Override
public TeamData read(Json json, JsonValue jsonData, Class type){
long[] blocks = jsonData.get("brokenBlocks").asLongArray();
Team team = Team.all[jsonData.getInt("team", 0)];
TeamData out = new TeamData(team, EnumSet.of(new Team[]{}));
out.brokenBlocks = new LongQueue(blocks);
return out;
}
});*/
json.setSerializer(ItemStack.class, new Serializer<ItemStack>(){
@Override
public void write(Json json, ItemStack object, Class knownType){

View File

@@ -1,25 +1,21 @@
package io.anuke.mindustry.io;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.Pixmap;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.Structs;
import io.anuke.arc.util.serialization.Json;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.game.SpawnGroup;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.MapIO.TileProvider;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.type.ContentType;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.MapIO.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock;
import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.LegacyColorMapper.*;
import io.anuke.mindustry.world.blocks.*;
import java.io.*;
import java.util.zip.InflaterInputStream;
import java.util.zip.*;
import static io.anuke.mindustry.Vars.*;
@@ -208,20 +204,6 @@ public class LegacyMapIO{
//place core
if(color == Color.rgba8888(Color.green)){
for(int dx = 0; dx < 3; dx++){
for(int dy = 0; dy < 3; dy++){
int worldx = dx - 1 + x;
int worldy = dy - 1 + y;
//multiblock parts
if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){
Tile write = tiles[worldx][worldy];
write.setBlock(BlockPart.get(dx - 1, dy - 1));
write.setTeam(Team.sharded);
}
}
}
//actual core parts
tile.setBlock(Blocks.coreShard);
tile.setTeam(Team.sharded);

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Pixmap.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.world.*;

View File

@@ -34,37 +34,23 @@ public class SaveIO{
return versions.get(version);
}
public static void saveToSlot(int slot){
FileHandle file = fileFor(slot);
public static void save(FileHandle file){
boolean exists = file.exists();
if(exists) file.moveTo(backupFileFor(file));
try{
write(fileFor(slot));
write(file);
}catch(Exception e){
if(exists) backupFileFor(file).moveTo(file);
throw new RuntimeException(e);
}
}
public static void loadFromSlot(int slot) throws SaveException{
load(fileFor(slot));
public static DataInputStream getStream(FileHandle file){
return new DataInputStream(new InflaterInputStream(file.read(bufferSize)));
}
public static DataInputStream getSlotStream(int slot){
return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize)));
}
public static DataInputStream getBackupSlotStream(int slot){
return new DataInputStream(new InflaterInputStream(backupFileFor(fileFor(slot)).read(bufferSize)));
}
public static boolean isSaveValid(int slot){
try{
getMeta(slot);
return true;
}catch(Exception e){
return false;
}
public static DataInputStream getBackupStream(FileHandle file){
return new DataInputStream(new InflaterInputStream(backupFileFor(file).read(bufferSize)));
}
public static boolean isSaveValid(FileHandle file){
@@ -85,11 +71,11 @@ public class SaveIO{
}
}
public static SaveMeta getMeta(int slot){
public static SaveMeta getMeta(FileHandle file){
try{
return getMeta(getSlotStream(slot));
return getMeta(getStream(file));
}catch(Exception e){
return getMeta(getBackupSlotStream(slot));
return getMeta(getBackupStream(file));
}
}
@@ -167,6 +153,7 @@ public class SaveIO{
}catch(Exception e){
throw new SaveException(e);
}finally{
world.setGenerating(false);
content.setTemporaryMapper(null);
}
}

View File

@@ -3,6 +3,9 @@ package io.anuke.mindustry.io;
import io.anuke.arc.collection.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*;
@@ -164,6 +167,7 @@ public abstract class SaveVersion extends SaveFileReader{
short floorid = stream.readShort();
short oreid = stream.readShort();
int consecutives = stream.readUnsignedByte();
if(content.block(floorid) == Blocks.air) floorid = Blocks.stone.id;
context.create(x, y, floorid, oreid, (short)0);
@@ -180,6 +184,7 @@ public abstract class SaveVersion extends SaveFileReader{
int x = i % width, y = i / width;
Block block = content.block(stream.readShort());
Tile tile = context.tile(x, y);
if(block == null) block = Blocks.air;
tile.setBlock(block);
if(tile.entity != null){
@@ -257,7 +262,7 @@ public abstract class SaveVersion extends SaveFileReader{
TeamData data = state.teams.get(team);
int blocks = stream.readInt();
for(int j = 0; j < blocks; j++){
data.brokenBlocks.addLast(new BrokenBlock(stream.readShort(), stream.readShort(), stream.readShort(), stream.readShort(), stream.readInt()));
data.brokenBlocks.addLast(new BrokenBlock(stream.readShort(), stream.readShort(), stream.readShort(), content.block(stream.readShort()).id, stream.readInt()));
}
}

View File

@@ -11,7 +11,7 @@ import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.ShooterTrait;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.net.Administration.TraceInfo;
import io.anuke.mindustry.net.Packets.AdminAction;
import io.anuke.mindustry.net.Packets.KickReason;
@@ -126,6 +126,8 @@ public class TypeIO{
if(!request.breaking){
buffer.putShort(request.block.id);
buffer.put((byte)request.rotation);
buffer.put(request.hasConfig ? (byte)1 : 0);
buffer.putInt(request.config);
}
}
}
@@ -148,7 +150,12 @@ public class TypeIO{
}else{ //place
short block = buffer.getShort();
byte rotation = buffer.get();
boolean hasConfig = buffer.get() == 1;
int config = buffer.getInt();
currentRequest = new BuildRequest(Pos.x(position), Pos.y(position), rotation, content.block(block));
if(hasConfig){
currentRequest.configure(config);
}
}
reqs[i] = (currentRequest);
@@ -167,6 +174,23 @@ public class TypeIO{
return KickReason.values()[buffer.get()];
}
@WriteClass(Rules.class)
public static void writeRules(ByteBuffer buffer, Rules rules){
String string = JsonIO.write(rules);
byte[] bytes = string.getBytes(charset);
buffer.putInt(bytes.length);
buffer.put(bytes);
}
@ReadClass(Rules.class)
public static Rules readRules(ByteBuffer buffer){
int length = buffer.getInt();
byte[] bytes = new byte[length];
buffer.get(bytes);
String string = new String(bytes, charset);
return JsonIO.read(Rules.class, string);
}
@WriteClass(Team.class)
public static void writeTeam(ByteBuffer buffer, Team reason){
buffer.put((byte)reason.ordinal());

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.io.versions;
import io.anuke.arc.function.Supplier;
import io.anuke.arc.func.Prov;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.Player;
@@ -62,7 +62,7 @@ Before addition of new units: [build 79 and below]
14 = Revenant
*/
public class LegacyTypeTable{
private static final Supplier[] build81Table = {
private static final Prov[] build81Table = {
Player::new,
Fire::new,
Puddle::new,
@@ -79,7 +79,7 @@ public class LegacyTypeTable{
Revenant::new
};
private static final Supplier[] build80Table = {
private static final Prov[] build80Table = {
Player::new,
Fire::new,
Puddle::new,
@@ -98,7 +98,7 @@ public class LegacyTypeTable{
Revenant::new
};
private static final Supplier[] build79Table = {
private static final Prov[] build79Table = {
Player::new,
Fire::new,
Puddle::new,
@@ -116,7 +116,7 @@ public class LegacyTypeTable{
Revenant::new
};
public static Supplier[] getTable(int build){
public static Prov[] getTable(int build){
if(build == -1 || build == 81){
//return most recent one since that's probably it; not guaranteed
return build81Table;

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.io.versions;
import io.anuke.arc.function.*;
import io.anuke.arc.func.*;
import io.anuke.mindustry.entities.traits.*;
import java.io.*;
@@ -13,7 +13,7 @@ public class Save1 extends Save2{
@Override
public void readEntities(DataInput stream) throws IOException{
Supplier[] table = LegacyTypeTable.getTable(lastReadBuild);
Prov[] table = LegacyTypeTable.getTable(lastReadBuild);
byte groups = stream.readByte();

View File

@@ -1,9 +1,9 @@
package io.anuke.mindustry.io.versions;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID;
import java.io.*;

View File

@@ -4,14 +4,17 @@ import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.filters.*;
import io.anuke.mindustry.type.*;
import static io.anuke.mindustry.Vars.maps;
import static io.anuke.mindustry.Vars.*;
public class Map implements Comparable<Map>{
public class Map implements Comparable<Map>, Publishable{
/** Whether this is a custom map. */
public final boolean custom;
/** Metadata. Author description, display name, etc. */
@@ -68,7 +71,7 @@ public class Map implements Comparable<Map>{
}
public FileHandle cacheFile(){
return Vars.mapPreviewDirectory.child(file.nameWithoutExtension() + "-cache.dat");
return Vars.mapPreviewDirectory.child(workshop ? file.parent().name() + "-workshop-cache.dat" : file.nameWithoutExtension() + "-cache.dat");
}
public void setHighScore(int score){
@@ -131,6 +134,78 @@ public class Map implements Comparable<Map>{
return tags.containsKey(name);
}
@Override
public String getSteamID(){
return tags.get("steamid");
}
@Override
public void addSteamID(String id){
tags.put("steamid", id);
ui.editor.editor.getTags().put("steamid", id);
try{
ui.editor.save();
}catch(Exception e){
Log.err(e);
}
Events.fire(new MapPublishEvent());
}
@Override
public void removeSteamID(){
tags.remove("steamid");
ui.editor.editor.getTags().remove("steamid");
try{
ui.editor.save();
}catch(Exception e){
Log.err(e);
}
}
@Override
public String steamTitle(){
return name();
}
@Override
public String steamDescription(){
return description();
}
@Override
public String steamTag(){
return "map";
}
@Override
public FileHandle createSteamFolder(String id){
FileHandle mapFile = tmpDirectory.child("map_" + id).child("map.msav");
file.copyTo(mapFile);
return mapFile.parent();
}
@Override
public FileHandle createSteamPreview(String id){
return previewFile();
}
@Override
public Array<String> extraTags(){
Gamemode mode = Gamemode.attack.valid(this) ? Gamemode.attack : Gamemode.survival;
return Array.with(mode.name());
}
@Override
public boolean prePublish(){
tags.put("author", player.name);
ui.editor.editor.getTags().put("author", tags.get("author"));
ui.editor.save();
return true;
}
@Override
public int compareTo(Map map){
int work = -Boolean.compare(workshop, map.workshop);

View File

@@ -8,7 +8,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.ctype.Content;
public class MapPreviewLoader extends TextureLoader{

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