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

This commit is contained in:
Anuken
2019-10-03 22:32:53 -04:00
184 changed files with 13512 additions and 8739 deletions

View File

@@ -2,6 +2,8 @@ package io.anuke.mindustry;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.assets.loaders.*;
import io.anuke.arc.audio.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
@@ -13,6 +15,7 @@ import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.net.Net;
import static io.anuke.arc.Core.*;
@@ -40,9 +43,15 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
batch = new SpriteBatch();
assets = new AssetManager();
assets.setLoader(Texture.class, "." + mapExtension, new MapPreviewLoader());
tree = new FileTree();
assets.setLoader(Sound.class, new SoundLoader(tree));
assets.setLoader(Music.class, new MusicLoader(tree));
assets.load("sprites/error.png", Texture.class);
atlas = TextureAtlas.blankAtlas();
Vars.net = new Net(platform.getNet());
mods = new Mods();
UI.loadSystemCursors();
@@ -71,6 +80,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
add(netServer = new NetServer());
add(netClient = new NetClient());
assets.load(mods);
assets.loadRun("contentinit", ContentLoader.class, () -> {
content.init();
content.load();
@@ -108,6 +119,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
listener.init();
}
super.resize(graphics.getWidth(), graphics.getHeight());
mods.each(Mod::init);
finished = true;
Events.fire(new ClientLoadEvent());
}
@@ -182,7 +194,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
if(assets.getCurrentLoading() != null){
String name = assets.getCurrentLoading().fileName.toLowerCase();
String key = name.contains("content") ? "content" : name.contains("msav") || name.contains("maps") ? "map" : name.contains("ogg") || name.contains("mp3") ? "sound" : name.contains("png") ? "image" : "system";
String key = name.contains("content") ? "content" : name.contains("mod") ? "mods" : name.contains("msav") || name.contains("maps") ? "map" : name.contains("ogg") || name.contains("mp3") ? "sound" : name.contains("png") ? "image" : "system";
font.draw(bundle.get("load." + key, ""), graphics.getWidth() / 2f, graphics.getHeight() / 2f - height / 2f - Scl.scl(10f), Align.center);
}
}

View File

@@ -18,19 +18,21 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import java.nio.charset.*;
import java.util.*;
import static io.anuke.arc.Core.settings;
import static io.anuke.arc.Core.*;
@SuppressWarnings("unchecked")
public class Vars implements Loadable{
/** Whether to load locales.*/
public static boolean loadLocales = true;
/** Maximum number of broken blocks. TODO implement or remove.*/
public static final int maxBrokenBlocks = 256;
/** IO buffer size. */
public static final int bufferSize = 8192;
/** global charset, since Android doesn't support the Charsets class */
@@ -43,6 +45,10 @@ public class Vars implements Loadable{
public static final String discordURL = "https://discord.gg/mindustry";
/** URL for sending crash reports to */
public static final String crashReportURL = "http://mins.us.to/report";
/** URL the links to the wiki's modding guide.*/
public static final String modGuideURL = "https://mindustrygame.github.io/wiki/modding/";
/** URL the links to the wiki's modding guide.*/
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?template=bug_report.md";
/** list of built-in servers.*/
public static final Array<String> defaultServers = Array.with(/*"mins.us.to"*/);
/** maximum distance between mine and core that supports automatic transferring */
@@ -120,8 +126,8 @@ public class Vars implements Loadable{
public static FileHandle tmpDirectory;
/** data subdirectory used for saves */
public static FileHandle saveDirectory;
/** data subdirectory used for plugins */
public static FileHandle pluginDirectory;
/** data subdirectory used for mods */
public static FileHandle modDirectory;
/** map file extension */
public static final String mapExtension = "msav";
/** save file extension */
@@ -130,6 +136,7 @@ public class Vars implements Loadable{
/** list of all locales that can be switched to */
public static Locale[] locales;
public static FileTree tree;
public static Net net;
public static ContentLoader content;
public static GameState state;
@@ -138,7 +145,7 @@ public class Vars implements Loadable{
public static DefaultWaves defaultWaves;
public static LoopControl loops;
public static Platform platform = new Platform(){};
public static Plugins plugins;
public static Mods mods;
public static World world;
public static Maps maps;
@@ -193,6 +200,9 @@ public class Vars implements Loadable{
Version.init();
if(tree == null) tree = new FileTree();
if(mods == null) mods = new Mods();
content = new ContentLoader();
loops = new LoopControl();
defaultWaves = new DefaultWaves();
@@ -240,15 +250,18 @@ public class Vars implements Loadable{
mapPreviewDirectory = dataDirectory.child("previews/");
saveDirectory = dataDirectory.child("saves/");
tmpDirectory = dataDirectory.child("tmp/");
pluginDirectory = dataDirectory.child("plugins/");
modDirectory = dataDirectory.child("mods/");
modDirectory.mkdirs();
mods.load();
maps.load();
}
public static void loadSettings(){
Core.settings.setAppName(appName);
if(steam){
if(steam || "steam".equals(Version.modifier)){
Core.settings.setDataDirectory(Core.files.local("saves/"));
}

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.async.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
@@ -32,7 +33,8 @@ public class Pathfinder implements Runnable{
/** handles task scheduling on the update thread. */
private TaskQueue queue = new TaskQueue();
/** current pathfinding thread */
private @Nullable Thread thread;
private @Nullable
Thread thread;
public Pathfinder(){
Events.on(WorldLoadEvent.class, event -> {
@@ -92,7 +94,11 @@ public class Pathfinder implements Runnable{
int x = tile.x, y = tile.y;
tile.getLinkedTiles(t -> tiles[t.x][t.y] = packTile(t));
tile.getLinkedTiles(t -> {
if(Structs.inBounds(t.x, t.y, tiles)){
tiles[t.x][t.y] = packTile(t);
}
});
//can't iterate through array so use the map, which should not lead to problems
for(PathData[] arr : pathMap){

View File

@@ -48,7 +48,7 @@ public class WaveSpawner{
for(SpawnGroup group : state.rules.spawns){
int spawned = group.getUnitsSpawned(state.wave - 1);
if(group.type.isFlying){
if(group.type.flying){
float spread = margin / 1.5f;
eachFlyerSpawn((spawnX, spawnY) -> {

View File

@@ -781,7 +781,7 @@ public class Blocks implements ContentList{
}};
copperWallLarge = new Wall("copper-wall-large"){{
requirements(Category.defense, ItemStack.mult(copperWall.buildRequirements, 4));
requirements(Category.defense, ItemStack.mult(copperWall.requirements, 4));
health = 80 * 4 * wallHealthMultiplier;
size = 2;
}};
@@ -792,7 +792,7 @@ public class Blocks implements ContentList{
}};
titaniumWallLarge = new Wall("titanium-wall-large"){{
requirements(Category.defense, ItemStack.mult(titaniumWall.buildRequirements, 4));
requirements(Category.defense, ItemStack.mult(titaniumWall.requirements, 4));
health = 110 * wallHealthMultiplier * 4;
size = 2;
}};
@@ -803,7 +803,7 @@ public class Blocks implements ContentList{
}};
thoriumWallLarge = new Wall("thorium-wall-large"){{
requirements(Category.defense, ItemStack.mult(thoriumWall.buildRequirements, 4));
requirements(Category.defense, ItemStack.mult(thoriumWall.requirements, 4));
health = 200 * wallHealthMultiplier * 4;
size = 2;
}};
@@ -814,7 +814,7 @@ public class Blocks implements ContentList{
}};
phaseWallLarge = new DeflectorWall("phase-wall-large"){{
requirements(Category.defense, ItemStack.mult(phaseWall.buildRequirements, 4));
requirements(Category.defense, ItemStack.mult(phaseWall.requirements, 4));
health = 150 * 4 * wallHealthMultiplier;
size = 2;
}};
@@ -825,7 +825,7 @@ public class Blocks implements ContentList{
}};
surgeWallLarge = new SurgeWall("surge-wall-large"){{
requirements(Category.defense, ItemStack.mult(surgeWall.buildRequirements, 4));
requirements(Category.defense, ItemStack.mult(surgeWall.requirements, 4));
health = 230 * 4 * wallHealthMultiplier;
size = 2;
}};
@@ -836,7 +836,7 @@ public class Blocks implements ContentList{
}};
doorLarge = new Door("door-large"){{
requirements(Category.defense, ItemStack.mult(door.buildRequirements, 4));
requirements(Category.defense, ItemStack.mult(door.requirements, 4));
openfx = Fx.dooropenlarge;
closefx = Fx.doorcloselarge;
health = 100 * 4 * wallHealthMultiplier;
@@ -1315,7 +1315,8 @@ public class Blocks implements ContentList{
requirements(Category.turret, ItemStack.with(Items.copper, 85, Items.lead, 45));
ammo(
Items.scrap, Bullets.flakScrap,
Items.lead, Bullets.flakLead
Items.lead, Bullets.flakLead,
Items.metaglass, Bullets.flakGlass
);
reload = 18f;
range = 170f;
@@ -1558,6 +1559,7 @@ public class Blocks implements ContentList{
cyclone = new ItemTurret("cyclone"){{
requirements(Category.turret, ItemStack.with(Items.copper, 200, Items.titanium, 125, Items.plastanium, 80));
ammo(
Items.metaglass, Bullets.flakGlass,
Items.blastCompound, Bullets.flakExplosive,
Items.plastanium, Bullets.flakPlastic,
Items.surgealloy, Bullets.flakSurge
@@ -1631,28 +1633,28 @@ public class Blocks implements ContentList{
produceTime = 2500;
size = 2;
maxSpawn = 1;
consumes.power(1.1f);
consumes.power(1.2f);
consumes.items();
}};
spiritFactory = new UnitFactory("spirit-factory"){{
requirements(Category.units, ItemStack.with(Items.metaglass, 45, Items.lead, 55, Items.silicon, 45));
type = UnitTypes.spirit;
produceTime = 3500;
produceTime = 4000;
size = 2;
maxSpawn = 2;
consumes.power(0.80f);
consumes.items(new ItemStack(Items.silicon, 15), new ItemStack(Items.lead, 15));
maxSpawn = 1;
consumes.power(1.2f);
consumes.items(new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30));
}};
phantomFactory = new UnitFactory("phantom-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 45, Items.thorium, 40, Items.lead, 55, Items.silicon, 105));
requirements(Category.units, ItemStack.with(Items.titanium, 50, Items.thorium, 60, Items.lead, 65, Items.silicon, 105));
type = UnitTypes.phantom;
produceTime = 3650;
produceTime = 4400;
size = 2;
maxSpawn = 2;
consumes.power(2f);
consumes.items(new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 20), new ItemStack(Items.titanium, 10));
maxSpawn = 1;
consumes.power(2.5f);
consumes.items(new ItemStack(Items.silicon, 50), new ItemStack(Items.lead, 30), new ItemStack(Items.titanium, 20));
}};
commandCenter = new CommandCenter("command-center"){{

View File

@@ -8,11 +8,9 @@ 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.entities.type.Bullet;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.*;
@@ -23,7 +21,7 @@ public class Bullets implements ContentList{
artilleryDense, arilleryPlastic, artilleryPlasticFrag, artilleryHoming, artlleryIncendiary, artilleryExplosive, artilleryUnit,
//flak
flakScrap, flakLead, flakPlastic, flakExplosive, flakSurge,
flakScrap, flakLead, flakPlastic, flakExplosive, flakSurge, flakGlass, glassFrag,
//missiles
missileExplosive, missileIncendiary, missileSurge, missileJavelin, missileSwarm, missileRevenant,
@@ -39,7 +37,7 @@ public class Bullets implements ContentList{
waterShot, cryoShot, slagShot, oilShot,
//environment, misc.
fireball, basicFlame, pyraFlame, driverBolt, healBullet, frag, eruptorShot,
fireball, basicFlame, pyraFlame, driverBolt, healBullet, healBulletBig, frag, eruptorShot,
//bombs
bombExplosive, bombIncendiary, bombOil;
@@ -57,7 +55,7 @@ public class Bullets implements ContentList{
splashDamage = 33f;
}};
artilleryPlasticFrag = new BasicBulletType(2.5f, 7, "bullet"){{
artilleryPlasticFrag = new BasicBulletType(2.5f, 10, "bullet"){{
bulletWidth = 10f;
bulletHeight = 12f;
bulletShrink = 1f;
@@ -134,6 +132,16 @@ public class Bullets implements ContentList{
frontColor = Pal.bulletYellow;
}};
glassFrag = new BasicBulletType(3f, 6, "bullet"){{
bulletWidth = 5f;
bulletHeight = 12f;
bulletShrink = 1f;
lifetime = 20f;
backColor = Pal.gray;
frontColor = Color.white;
despawnEffect = Fx.none;
}};
flakLead = new FlakBulletType(4.2f, 3){{
lifetime = 60f;
ammoMultiplier = 4f;
@@ -157,8 +165,23 @@ public class Bullets implements ContentList{
splashDamageRadius = 24f;
}};
flakGlass = new FlakBulletType(4f, 3){{
lifetime = 70f;
ammoMultiplier = 5f;
shootEffect = Fx.shootSmall;
reloadMultiplier = 0.8f;
bulletWidth = 6f;
bulletHeight = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 30f;
splashDamageRadius = 26f;
fragBullet = glassFrag;
fragBullets = 6;
}};
flakPlastic = new FlakBulletType(4f, 6){{
splashDamageRadius = 50f;
splashDamage = 25f;
fragBullet = artilleryPlasticFrag;
fragBullets = 6;
hitEffect = Fx.plasticExplosion;
@@ -376,43 +399,13 @@ public class Bullets implements ContentList{
statusDuration = 10f;
}};
healBullet = new BulletType(5.2f, 13){
float healPercent = 3f;
healBullet = new HealBulletType(5.2f, 13){{
healPercent = 3f;
}};
{
shootEffect = Fx.shootHeal;
smokeEffect = Fx.hitLaser;
hitEffect = Fx.hitLaser;
despawnEffect = Fx.hitLaser;
collidesTeam = true;
}
@Override
public boolean collides(Bullet b, Tile tile){
return tile.getTeam() != b.getTeam() || tile.entity.healthf() < 1f;
}
@Override
public void draw(Bullet b){
Draw.color(Pal.heal);
Lines.stroke(2f);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 7f);
Draw.color(Color.white);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 3f);
Draw.reset();
}
@Override
public void hitTile(Bullet b, Tile tile){
super.hit(b);
tile = tile.link();
if(tile.entity != null && tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
Effects.effect(Fx.healBlockFull, Pal.heal, tile.drawx(), tile.drawy(), tile.block().size);
tile.entity.healBy(healPercent / 100f * tile.entity.maxHealth());
}
}
};
healBulletBig = new HealBulletType(5.2f, 15){{
healPercent = 5.5f;
}};
fireball = new BulletType(1f, 4){
{

View File

@@ -11,7 +11,6 @@ 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.type.Item.*;
import static io.anuke.mindustry.Vars.tilesize;
@@ -552,7 +551,7 @@ public class Fx implements ContentList{
float length = 20f * e.finpow();
float size = 7f * e.fout();
Draw.rect(((Item)e.data).icon(Icon.large), e.x + Angles.trnsx(e.rotation, length), e.y + Angles.trnsy(e.rotation, length), size, size);
Draw.rect(((Item)e.data).icon(Cicon.medium), e.x + Angles.trnsx(e.rotation, length), e.y + Angles.trnsy(e.rotation, length), size, size);
});

View File

@@ -220,7 +220,7 @@ public class Mechs implements ContentList{
dart = new Mech("dart-ship", true){
{
drillPower = 1;
mineSpeed = 0.9f;
mineSpeed = 3f;
speed = 0.5f;
drag = 0.09f;
health = 200f;

View File

@@ -13,6 +13,7 @@ public class TechTree implements ContentList{
@Override
public void load(){
TechNode.context = null;
all = new Array<>();
root = node(coreShard, () -> {
@@ -302,19 +303,24 @@ public class TechTree implements ContentList{
});
}
private TechNode node(Block block, Runnable children){
ItemStack[] requirements = new ItemStack[block.buildRequirements.length];
private static TechNode node(Block block, Runnable children){
ItemStack[] requirements = new ItemStack[block.requirements.length];
for(int i = 0; i < requirements.length; i++){
requirements[i] = new ItemStack(block.buildRequirements[i].item, 30 + block.buildRequirements[i].amount * 6);
requirements[i] = new ItemStack(block.requirements[i].item, 30 + block.requirements[i].amount * 6);
}
return new TechNode(block, requirements, children);
}
private TechNode node(Block block){
private static TechNode node(Block block){
return node(block, () -> {});
}
public static void create(Block parent, Block block){
TechNode.context = all.find(t -> t.block == parent);
node(block, () -> {});
}
public static class TechNode{
static TechNode context;
@@ -322,19 +328,22 @@ public class TechTree implements ContentList{
public final ItemStack[] requirements;
public final Array<TechNode> children = new Array<>();
TechNode(Block block, ItemStack[] requirements, Runnable children){
if(context != null){
context.children.add(this);
TechNode(TechNode ccontext, Block block, ItemStack[] requirements, Runnable children){
if(ccontext != null){
ccontext.children.add(this);
}
this.block = block;
this.requirements = requirements;
TechNode last = context;
context = this;
children.run();
context = last;
context = ccontext;
all.add(this);
}
TechNode(Block block, ItemStack[] requirements, Runnable children){
this(context, block, requirements, children);
}
}
}

View File

@@ -17,8 +17,8 @@ public class UnitTypes implements ContentList{
@Override
public void load(){
draug = new UnitType("draug", Draug.class, Draug::new){{
isFlying = true;
draug = new UnitType("draug", Draug::new){{
flying = true;
drag = 0.01f;
speed = 0.3f;
maxVelocity = 1.2f;
@@ -32,13 +32,13 @@ public class UnitTypes implements ContentList{
}};
}};
spirit = new UnitType("spirit", Spirit.class, Spirit::new){{
isFlying = true;
spirit = new UnitType("spirit", Spirit::new){{
flying = true;
drag = 0.01f;
speed = 0.4f;
speed = 0.42f;
maxVelocity = 1.6f;
range = 50f;
health = 60;
health = 100;
engineSize = 1.8f;
engineOffset = 5.7f;
weapon = new Weapon("heal-blaster"){{
@@ -48,22 +48,21 @@ public class UnitTypes implements ContentList{
roundrobin = true;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBullet;
bullet = Bullets.healBulletBig;
shootSound = Sounds.pew;
}};
}};
phantom = new UnitType("phantom", Phantom.class, Phantom::new){{
isFlying = true;
phantom = new UnitType("phantom", Phantom::new){{
flying = true;
drag = 0.01f;
mass = 2f;
speed = 0.45f;
maxVelocity = 1.9f;
range = 70f;
itemCapacity = 70;
health = 220;
buildPower = 0.9f;
minePower = 1.1f;
health = 400;
buildPower = 1f;
engineOffset = 6.5f;
toMine = ObjectSet.with(Items.lead, Items.copper, Items.titanium);
weapon = new Weapon("heal-blaster"){{
@@ -77,7 +76,7 @@ public class UnitTypes implements ContentList{
}};
}};
dagger = new UnitType("dagger", Dagger.class, Dagger::new){{
dagger = new UnitType("dagger", Dagger::new){{
maxVelocity = 1.1f;
speed = 0.2f;
drag = 0.4f;
@@ -93,7 +92,7 @@ public class UnitTypes implements ContentList{
}};
}};
crawler = new UnitType("crawler", Crawler.class, Crawler::new){{
crawler = new UnitType("crawler", Crawler::new){{
maxVelocity = 1.27f;
speed = 0.285f;
drag = 0.4f;
@@ -124,7 +123,7 @@ public class UnitTypes implements ContentList{
}};
}};
titan = new UnitType("titan", Titan.class, Titan::new){{
titan = new UnitType("titan", Titan::new){{
maxVelocity = 0.8f;
speed = 0.22f;
drag = 0.4f;
@@ -146,7 +145,7 @@ public class UnitTypes implements ContentList{
}};
}};
fortress = new UnitType("fortress", Fortress.class, Fortress::new){{
fortress = new UnitType("fortress", Fortress::new){{
maxVelocity = 0.78f;
speed = 0.15f;
drag = 0.4f;
@@ -168,7 +167,7 @@ public class UnitTypes implements ContentList{
}};
}};
eruptor = new UnitType("eruptor", Eruptor.class, Eruptor::new){{
eruptor = new UnitType("eruptor", Eruptor::new){{
maxVelocity = 0.81f;
speed = 0.16f;
drag = 0.4f;
@@ -190,7 +189,7 @@ public class UnitTypes implements ContentList{
}};
}};
chaosArray = new UnitType("chaos-array", Dagger.class, Dagger::new){{
chaosArray = new UnitType("chaos-array", Dagger::new){{
maxVelocity = 0.68f;
speed = 0.12f;
drag = 0.4f;
@@ -214,7 +213,7 @@ public class UnitTypes implements ContentList{
}};
}};
eradicator = new UnitType("eradicator", Dagger.class, Dagger::new){{
eradicator = new UnitType("eradicator", Dagger::new){{
maxVelocity = 0.68f;
speed = 0.12f;
drag = 0.4f;
@@ -239,12 +238,12 @@ public class UnitTypes implements ContentList{
}};
}};
wraith = new UnitType("wraith", Wraith.class, Wraith::new){{
wraith = new UnitType("wraith", Wraith::new){{
speed = 0.3f;
maxVelocity = 1.9f;
drag = 0.01f;
mass = 1.5f;
isFlying = true;
flying = true;
health = 75;
engineOffset = 5.5f;
range = 140f;
@@ -258,13 +257,13 @@ public class UnitTypes implements ContentList{
}};
}};
ghoul = new UnitType("ghoul", Ghoul.class, Ghoul::new){{
ghoul = new UnitType("ghoul", Ghoul::new){{
health = 220;
speed = 0.2f;
maxVelocity = 1.4f;
mass = 3f;
drag = 0.01f;
isFlying = true;
flying = true;
targetAir = false;
engineOffset = 7.8f;
range = 140f;
@@ -282,7 +281,7 @@ public class UnitTypes implements ContentList{
}};
}};
revenant = new UnitType("revenant", Revenant.class, Revenant::new){{
revenant = new UnitType("revenant", Revenant::new){{
health = 1000;
mass = 5f;
hitsize = 20f;
@@ -291,7 +290,7 @@ public class UnitTypes implements ContentList{
drag = 0.01f;
range = 80f;
shootCone = 40f;
isFlying = true;
flying = true;
rotateWeapon = true;
engineOffset = 12f;
engineSize = 3f;
@@ -313,7 +312,7 @@ public class UnitTypes implements ContentList{
}};
}};
lich = new UnitType("lich", Revenant.class, Revenant::new){{
lich = new UnitType("lich", Revenant::new){{
health = 6000;
mass = 20f;
hitsize = 40f;
@@ -322,7 +321,7 @@ public class UnitTypes implements ContentList{
drag = 0.02f;
range = 80f;
shootCone = 20f;
isFlying = true;
flying = true;
rotateWeapon = true;
engineOffset = 21;
engineSize = 5.3f;
@@ -346,7 +345,7 @@ public class UnitTypes implements ContentList{
}};
}};
reaper = new UnitType("reaper", Revenant.class, Revenant::new){{
reaper = new UnitType("reaper", Revenant::new){{
health = 11000;
mass = 30f;
hitsize = 56f;
@@ -355,7 +354,7 @@ public class UnitTypes implements ContentList{
drag = 0.02f;
range = 80f;
shootCone = 30f;
isFlying = true;
flying = true;
rotateWeapon = true;
engineOffset = 40;
engineSize = 7.3f;

View File

@@ -11,6 +11,7 @@ import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import static io.anuke.arc.Core.files;
import static io.anuke.mindustry.Vars.mods;
/**
* Loads all game content.
@@ -41,6 +42,14 @@ public class ContentLoader{
new LegacyColorMapper(),
};
/** Clears all initialized content.*/
public void clear(){
contentNameMap = new ObjectMap[ContentType.values().length];
contentMap = new Array[ContentType.values().length];
initialization = new ObjectSet<>();
loaded = false;
}
/** Creates all content types. */
public void createContent(){
if(loaded){
@@ -57,20 +66,11 @@ public class ContentLoader{
list.load();
}
for(ContentType type : ContentType.values()){
for(Content c : contentMap[type.ordinal()]){
if(c instanceof MappableContent){
String name = ((MappableContent)c).name;
if(contentNameMap[type.ordinal()].containsKey(name)){
throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + name + "')");
}
contentNameMap[type.ordinal()].put(name, (MappableContent)c);
}
}
if(mods != null){
mods.loadContent();
}
//set up ID mapping
//check up ID mapping, make sure it's linear
for(Array<Content> arr : contentMap){
for(int i = 0; i < arr.size; i++){
int id = arr.get(i).id;
@@ -109,6 +109,7 @@ public class ContentLoader{
for(ContentType type : ContentType.values()){
for(Content content : contentMap[type.ordinal()]){
//TODO catch error and display it per mod
callable.accept(content);
}
}
@@ -138,6 +139,14 @@ public class ContentLoader{
public void handleContent(Content content){
contentMap[content.getContentType().ordinal()].add(content);
}
public void handleMappableContent(MappableContent content){
if(contentNameMap[content.getContentType().ordinal()].containsKey(content.name)){
throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')");
}
contentNameMap[content.getContentType().ordinal()].put(content.name, content);
}
public void setTemporaryMapper(MappableContent[][] temporaryMapper){

View File

@@ -91,6 +91,7 @@ public class Control implements ApplicationListener, Loadable{
hiscore = true;
world.getMap().setHighScore(state.wave);
}
Sounds.wave.play();
});
@@ -388,7 +389,10 @@ public class Control implements ApplicationListener, Loadable{
saves.update();
//update and load any requested assets
assets.update();
try{
assets.update();
}catch(Exception ignored){
}
input.updateState();

View File

@@ -0,0 +1,34 @@
package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.assets.loaders.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
/** Handles files in a modded context. */
public class FileTree implements FileHandleResolver{
private ObjectMap<String, FileHandle> files = new ObjectMap<>();
public void addFile(String path, FileHandle f){
files.put(path, f);
}
/** Gets an asset file.*/
public FileHandle get(String path){
if(files.containsKey(path)){
return files.get(path);
}else{
return Core.files.internal(path);
}
}
/** Clears all mod files.*/
public void clear(){
files.clear();
}
@Override
public FileHandle resolve(String fileName){
return get(fileName);
}
}

View File

@@ -16,6 +16,7 @@ import io.anuke.mindustry.type.*;
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.*;
import static io.anuke.mindustry.Vars.*;
@@ -31,19 +32,24 @@ public class Logic implements ApplicationListener{
public Logic(){
Events.on(WaveEvent.class, event -> {
for(Player p : playerGroup.all()){
p.respawns = state.rules.respawns;
}
if(world.isZone()){
world.getZone().updateWave(state.wave);
}
for (Player p : playerGroup.all()) {
p.respawns = state.rules.respawns;
}
});
Events.on(BlockDestroyEvent.class, event -> {
//blocks that get broken are appended to the team's broken block queue
Tile tile = event.tile;
Block block = tile.block();
//skip null entities or nukes, for obvious reasons
if(tile.entity == null || tile.block() instanceof NuclearReactor) return;
if(block instanceof BuildBlock){
BuildEntity entity = tile.entity();
//update block to reflect the fact that something was being constructed
@@ -56,7 +62,33 @@ public class Logic implements ApplicationListener{
}
TeamData data = state.teams.get(tile.getTeam());
data.brokenBlocks.addFirst(BrokenBlock.get(tile.x, tile.y, tile.rotation(), block.id));
//remove existing blocks that have been placed here.
//painful O(n) iteration + copy
for(int i = 0; i < data.brokenBlocks.size; i++){
BrokenBlock b = data.brokenBlocks.get(i);
if(b.x == tile.x && b.y == tile.y){
data.brokenBlocks.removeIndex(i);
break;
}
}
data.brokenBlocks.addFirst(new BrokenBlock(tile.x, tile.y, tile.rotation(), block.id, tile.entity.config()));
});
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;
}
}
}
});
}

View File

@@ -76,6 +76,7 @@ public class NetClient implements ApplicationListener{
ConnectPacket c = new ConnectPacket();
c.name = player.name;
c.mods = mods.getModStrings();
c.mobile = mobile;
c.versionType = Version.type;
c.color = Color.rgba8888(player.color);
@@ -235,7 +236,7 @@ public class NetClient implements ApplicationListener{
netClient.disconnectQuietly();
state.set(State.menu);
logic.reset();
ui.showText("$disconnect", reason);
ui.showText("$disconnect", reason, Align.left);
ui.loadfrag.hide();
}
@@ -329,6 +330,11 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onStateSnapshot(float waveTime, int wave, int enemies, short coreDataLen, byte[] coreData){
try{
if(wave > state.wave){
state.wave = wave;
Events.fire(new WaveEvent());
}
state.wavetime = waveTime;
state.wave = wave;
state.enemies = enemies;

View File

@@ -104,6 +104,23 @@ public class NetServer implements ApplicationListener{
return;
}
Array<String> extraMods = packet.mods.copy();
Array<String> missingMods = mods.getIncompatibility(extraMods);
if(!extraMods.isEmpty() || !missingMods.isEmpty()){
//can't easily be localized since kick reasons can't have formatted text with them
StringBuilder result = new StringBuilder("[accent]Incompatible mods![]\n\n");
if(!missingMods.isEmpty()){
result.append("Missing:[lightgray]\n").append("> ").append(missingMods.toString("\n> "));
result.append("[]\n");
}
if(!extraMods.isEmpty()){
result.append("Unnecessary mods:[lightgray]\n").append("> ").append(extraMods.toString("\n> "));
}
con.kick(result.toString());
}
if(!admins.isWhitelisted(packet.uuid, packet.usid)){
info.adminUsid = packet.usid;
info.lastName = packet.name;
@@ -200,6 +217,11 @@ public class NetServer implements ApplicationListener{
registerCommands();
}
@Override
public void init(){
mods.each(mod -> mod.registerClientCommands(clientCommands));
}
private void registerCommands(){
clientCommands.<Player>register("help", "[page]", "Lists all commands.", (args, player) -> {
if(args.length > 0 && !Strings.canParseInt(args[0])){
@@ -262,7 +284,7 @@ public class NetServer implements ApplicationListener{
}
boolean checkPass(){
if(votes >= votesRequired() && target.isAdded() && target.con.isConnected()){
if(votes >= votesRequired()){
Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] {0}[orange] will be banned from the server for {1} minutes.", target.name, (kickDuration/60)));
target.getInfo().lastKicked = Time.millis() + kickDuration*1000;
playerGroup.all().each(p -> p.uuid != null && p.uuid.equals(target.uuid), p -> p.con.kick(KickReason.vote));

View File

@@ -37,6 +37,10 @@ public interface Platform{
/** 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(){}

View File

@@ -18,12 +18,10 @@ import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.effect.GroundEffectEntity.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.EffectEntity;
import io.anuke.mindustry.game.EventType.*;
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.world.*;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import static io.anuke.arc.Core.*;
@@ -241,6 +239,8 @@ public class Renderer implements ApplicationListener{
blocks.drawBlocks(Layer.block);
blocks.drawFog();
blocks.drawBroken();
Draw.shader(Shaders.blockbuild, true);
blocks.drawBlocks(Layer.placement);
Draw.shader();
@@ -311,7 +311,7 @@ public class Renderer implements ApplicationListener{
float fract = landTime / Fx.coreLand.lifetime;
TileEntity entity = player.getClosestCore();
TextureRegion reg = entity.block.icon(Block.Icon.full);
TextureRegion reg = entity.block.icon(Cicon.full);
float scl = Scl.scl(4f) / camerascale;
float s = reg.getWidth() * Draw.scl * scl * 4f * fract;

View File

@@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
public DeployDialog deploy;
public TechTreeDialog tech;
public MinimapDialog minimap;
public ModsDialog mods;
public Cursor drillCursor, unloadCursor;
@@ -108,6 +109,7 @@ public class UI implements ApplicationListener, Loadable{
ClickListener.clicked = () -> Sounds.press.play();
Colors.put("accent", Pal.accent);
Colors.put("unlaunched", Color.valueOf("8982ed"));
Colors.put("highlight", Pal.accent.cpy().lerp(Color.white, 0.3f));
Colors.put("stat", Pal.stat);
loadExtraCursors();
@@ -222,6 +224,7 @@ public class UI implements ApplicationListener, Loadable{
deploy = new DeployDialog();
tech = new TechTreeDialog();
minimap = new MinimapDialog();
mods = new ModsDialog();
Group group = Core.scene.root;
@@ -281,7 +284,7 @@ public class UI implements ApplicationListener, Loadable{
new Dialog(titleText){{
cont.margin(30).add(dtext).padRight(6f);
TextFieldFilter filter = inumeric ? TextFieldFilter.digitsOnly : (f, c) -> true;
TextField field = cont.addField(def, t -> {}).size(170f, 50f).get();
TextField field = cont.addField(def, t -> {}).size(330f, 50f).get();
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
buttons.defaults().size(120, 54).pad(4);
buttons.addButton("$ok", () -> {
@@ -358,11 +361,15 @@ public class UI implements ApplicationListener, Loadable{
}
public void showText(String titleText, String text){
showText(titleText, text, Align.center);
}
public void showText(String titleText, String text, int align){
new Dialog(titleText){{
cont.row();
cont.addImage().width(400f).pad(2).colspan(2).height(4f).color(Pal.accent);
cont.row();
cont.add(text).width(400f).wrap().get().setAlignment(Align.center, Align.center);
cont.add(text).width(400f).wrap().get().setAlignment(align, align);
cont.row();
buttons.addButton("$ok", this::hide).size(90, 50).pad(4);
}}.show();
@@ -410,6 +417,34 @@ public class UI implements ApplicationListener, Loadable{
dialog.show();
}
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.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.addButton(no, dialog::hide);
dialog.buttons.addButton(yes, () -> {
dialog.hide();
confirmed.run();
});
dialog.keyDown(KeyCode.ESCAPE, dialog::hide);
dialog.keyDown(KeyCode.BACK, dialog::hide);
dialog.show();
}
public void showOkText(String title, String text, Runnable confirmed){
FloatingDialog dialog = new FloatingDialog(title);
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.addButton("$ok", () -> {
dialog.hide();
confirmed.run();
});
dialog.show();
}
public String formatAmount(int number){
if(number >= 1000000){
return Strings.fixed(number / 1000000f, 1) + "[gray]mil[]";

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.core;
import io.anuke.annotations.Annotations.*;
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.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*;
@@ -82,7 +82,8 @@ public class World{
return height()*tilesize;
}
public @Nullable Tile tile(int pos){
public @Nullable
Tile tile(int pos){
return tiles == null ? null : tile(Pos.x(pos), Pos.y(pos));
}

View File

@@ -15,6 +15,7 @@ import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
@@ -23,7 +24,7 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.ui.Styles;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
@@ -147,13 +148,19 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(steam){
menu.cont.addImageTextButton("$editor.publish.workshop", Icon.linkSmall, () -> {
if(editor.getTags().containsKey("steamid")){
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"));
return;
}
Map map = save();
if(editor.getTags().containsKey("steamid") && map != null){
platform.viewMapListingInfo(map);
return;
}
if(map == null) return;
if(map.tags.get("description", "").length() < 4){
@@ -167,7 +174,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
platform.publishMap(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? "$view.workshop" : "$editor.publish.workshop"));
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
menu.cont.row();
}
@@ -208,14 +215,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
return;
}
Vector2 v = pane.stageToLocalCoordinates(Core.input.mouse());
if(v.x >= 0 && v.y >= 0 && v.x <= pane.getWidth() && v.y <= pane.getHeight()){
Core.scene.setScrollFocus(pane);
}else{
Core.scene.setScrollFocus(null);
}
if(Core.scene != null && Core.scene.getKeyboardFocus() == this){
doInput();
}
@@ -287,7 +286,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
});
}
public Map save(){
public @Nullable Map save(){
boolean isEditor = state.rules.editor;
state.rules.editor = false;
String name = editor.getTags().get("name", "").trim();
@@ -681,6 +680,11 @@ public class MapEditorDialog extends Dialog implements Disposable{
pane = new ScrollPane(content);
pane.setFadeScrollBars(false);
pane.setOverscroll(true, false);
pane.exited(() -> {
if(pane.hasScroll()){
Core.scene.setScrollFocus(view);
}
});
ButtonGroup<ImageButton> group = new ButtonGroup<>();
int i = 0;
@@ -698,7 +702,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
});
for(Block block : blocksOut){
TextureRegion region = block.icon(Block.Icon.medium);
TextureRegion region = block.icon(Cicon.medium);
if(!Core.atlas.isFound(region)) continue;

View File

@@ -388,7 +388,7 @@ public class MapGenerateDialog extends FloatingDialog{
GenTile tile = buffer1[px][py];
color = MapIO.colorFor(content.block(tile.floor), content.block(tile.block), content.block(tile.ore), Team.derelict);
}
pixmap.drawPixel(px, pixmap.getHeight() - 1 - py, color);
pixmap.draw(px, pixmap.getHeight() - 1 - py, color);
}
}

View File

@@ -59,18 +59,25 @@ public class MapInfoDialog extends FloatingDialog{
t.row();
t.add("$editor.rules").padRight(8).left();
t.addButton("$edit", () -> ruleInfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules())).left().width(200f);
t.addButton("$edit", () -> {
ruleInfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules());
hide();
}).left().width(200f);
t.row();
t.add("$editor.waves").padRight(8).left();
t.addButton("$edit", waveInfo::show).left().width(200f);
t.addButton("$edit", () -> {
waveInfo.show();
hide();
}).left().width(200f);
t.row();
t.add("$editor.generation").padRight(8).left();
t.addButton("$edit",
() -> generate.show(Vars.maps.readFilters(editor.getTags().get("genfilters", "")),
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)))
).left().width(200f);
t.addButton("$edit", () -> {
generate.show(Vars.maps.readFilters(editor.getTags().get("genfilters", "")),
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)));
hide();
}).left().width(200f);
name.change();
description.change();

View File

@@ -56,10 +56,16 @@ public class MapView extends Element implements GestureListener{
public boolean mouseMoved(InputEvent event, float x, float y){
mousex = x;
mousey = y;
requestScroll();
return false;
}
@Override
public void enter(InputEvent event, float x, float y, int pointer, Element fromActor){
requestScroll();
}
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
if(pointer != 0){

View File

@@ -140,7 +140,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.iconRegion).size(30f).padRight(3);
b.addImage(group.type.icon(Cicon.medium)).size(32f).padRight(3);
b.add(group.type.localizedName).color(Pal.accent);
}, () -> showUpdate(group)).pad(-6f).padBottom(0f);
@@ -221,7 +221,7 @@ public class WaveInfoDialog extends FloatingDialog{
for(UnitType type : content.units()){
dialog.cont.addButton(t -> {
t.left();
t.addImage(type.iconRegion).size(40f).padRight(2f);
t.addImage(type.icon(Cicon.medium)).size(40f).padRight(2f);
t.add(type.localizedName);
}, () -> {
lastType = type;
@@ -253,7 +253,7 @@ public class WaveInfoDialog extends FloatingDialog{
for(int j = 0; j < spawned.length; j++){
if(spawned[j] > 0){
UnitType type = content.getByID(ContentType.unit, j);
table.addImage(type.iconRegion).size(30f).padRight(4);
table.addImage(type.icon(Cicon.medium)).size(8f * 4f).padRight(4);
table.add(spawned[j] + "x").color(Color.lightGray).padRight(6);
table.row();
}

View File

@@ -20,6 +20,7 @@ public class Units{
private static float cdist;
private static boolean boolResult;
/** @return whether this player can interact with a specific tile. if either of these are null, returns true.*/
public static boolean canInteract(Player player, Tile tile){
return player == null || tile == null || tile.interactable(player.getTeam());
}

View File

@@ -0,0 +1,50 @@
package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
public class HealBulletType extends BulletType{
protected float healPercent = 3f;
public HealBulletType(float speed, float damage){
super(speed, damage);
shootEffect = Fx.shootHeal;
smokeEffect = Fx.hitLaser;
hitEffect = Fx.hitLaser;
despawnEffect = Fx.hitLaser;
collidesTeam = true;
}
@Override
public boolean collides(Bullet b, Tile tile){
return tile.getTeam() != b.getTeam() || tile.entity.healthf() < 1f;
}
@Override
public void draw(Bullet b){
Draw.color(Pal.heal);
Lines.stroke(2f);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 7f);
Draw.color(Color.white);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 3f);
Draw.reset();
}
@Override
public void hitTile(Bullet b, Tile tile){
super.hit(b);
tile = tile.link();
if(tile.entity != null && tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
Effects.effect(Fx.healBlockFull, Pal.heal, tile.drawx(), tile.drawy(), tile.block().size);
tile.entity.healBy(healPercent / 100f * tile.entity.maxHealth());
}
}
}

View File

@@ -47,10 +47,8 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
@Remote(called = Loc.server)
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
if(tile == null || tile.entity == null || tile.entity.items == null) return;
if(!Units.canInteract(player, tile)) return;
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
Time.run(i * 3, () -> create(item, x, y, tile, () -> {
}));
Time.run(i * 3, () -> create(item, x, y, tile, () -> {}));
}
tile.entity.items.add(item, amount);
}

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.Queue;
import io.anuke.arc.collection.*;
@@ -8,6 +7,7 @@ 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.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.*;
@@ -104,7 +104,11 @@ public interface BuilderTrait extends Entity, TeamTrait{
if(current.breaking){
entity.deconstruct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
}else{
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)){
if(current.hasConfig){
Call.onTileConfig(null, tile, current.config);
}
}
}
current.progress = entity.progress;
@@ -200,7 +204,8 @@ public interface BuilderTrait extends Entity, TeamTrait{
* Return the build requests currently active, or the one at the top of the queue.
* May return null.
*/
default @Nullable BuildRequest buildRequest(){
default @Nullable
BuildRequest buildRequest(){
return buildQueue().size == 0 ? null : buildQueue().first();
}
@@ -256,6 +261,8 @@ public interface BuilderTrait extends Entity, TeamTrait{
public final int x, y, rotation;
public final Block block;
public final boolean breaking;
public boolean hasConfig;
public int config;
public float progress;
public boolean initialized;
@@ -278,6 +285,12 @@ public interface BuilderTrait extends Entity, TeamTrait{
this.breaking = true;
}
public BuildRequest configure(int config){
this.config = config;
this.hasConfig = true;
return this;
}
public Tile tile(){
return world.tile(x, y);
}

View File

@@ -6,6 +6,7 @@ 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.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
@@ -93,7 +94,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
}
public @Nullable Tile getSpawner(){
public @Nullable
Tile getSpawner(){
return world.tile(spawner);
}
@@ -234,7 +236,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
@Override
public TextureRegion getIconRegion(){
return type.iconRegion;
return type.icon(Cicon.full);
}
@Override
@@ -263,7 +265,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
@Override
public boolean isFlying(){
return type.isFlying;
return type.flying;
}
@Override

View File

@@ -10,6 +10,7 @@ import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
@@ -48,7 +49,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public float baseRotation;
public float pointerX, pointerY;
public String name = "noname";
public @Nullable String uuid, usid;
public @Nullable
String uuid, usid;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping;
public float boostHeat, shootHeat, destructTime;
public boolean achievedFlight;
@@ -158,7 +160,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
@Override
public TextureRegion getIconRegion(){
return mech.iconRegion;
return mech.icon(Cicon.full);
}
@Override
@@ -279,7 +281,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public void drawShadow(float offsetX, float offsetY){
float scl = mech.flying ? 1f : boostHeat / 2f;
Draw.rect(mech.iconRegion, x + offsetX * scl, y + offsetY * scl, rotation - 90);
Draw.rect(getIconRegion(), x + offsetX * scl, y + offsetY * scl, rotation - 90);
}
@Override

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.collection.ObjectSet;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.HealthTrait;
import io.anuke.mindustry.entities.traits.TargetTrait;
@@ -230,6 +231,11 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
return proximity;
}
/** Tile configuration. Defaults to 0. Used for block rebuilding. */
public int config(){
return 0;
}
@Override
public void removed(){
if(sound != null){

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
@@ -9,6 +8,7 @@ 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.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
@@ -216,10 +216,14 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
float cx = x - fsize/2f, cy = y - fsize/2f;
for(Team team : Team.all){
avoid(unitGroups[team.ordinal()].intersect(cx, cy, fsize, fsize));
if(team != getTeam() || !(this instanceof Player)){
avoid(unitGroups[team.ordinal()].intersect(cx, cy, fsize, fsize));
}
}
avoid(playerGroup.intersect(cx, cy, fsize, fsize));
if(!(this instanceof Player)){
avoid(playerGroup.intersect(cx, cy, fsize, fsize));
}
velocity.add(moveVector.x / mass() * Time.delta(), moveVector.y / mass() * Time.delta());
}
@@ -227,7 +231,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
float radScl = 1.5f;
for(Unit en : arr){
if(en.isFlying() != isFlying()) continue;
if(en.isFlying() != isFlying() || (en instanceof Player && en.getTeam() != getTeam())) continue;
float dst = dst(en);
float scl = Mathf.clamp(1f - dst / (getSize()/(radScl*2f) + en.getSize()/(radScl*2f)));
moveVector.add(Tmp.v1.set((x - en.x) * scl, (y - en.y) * scl).limit(0.4f));
@@ -403,7 +407,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
float size = (itemSize + Mathf.absin(Time.time(), 5f, 1f)) * itemtime;
Draw.mixcol(Pal.accent, Mathf.absin(Time.time(), 5f, 0.5f));
Draw.rect(item.item.icon(Item.Icon.large),
Draw.rect(item.item.icon(Cicon.medium),
x + Angles.trnsx(rotation + 180f, backTrns),
y + Angles.trnsy(rotation + 180f, backTrns),
size, size, rotation);

View File

@@ -2,7 +2,6 @@ package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.mindustry.entities.type.FlyingUnit;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag;

View File

@@ -1,23 +1,19 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.Core;
import io.anuke.arc.Events;
import io.anuke.arc.collection.IntIntMap;
import io.anuke.arc.collection.Queue;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.game.EventType.BuildSelectEvent;
import io.anuke.mindustry.game.Teams.TeamData;
import io.anuke.mindustry.gen.BrokenBlock;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BuildBlock;
import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.BuildBlock.*;
import java.io.*;
@@ -45,7 +41,7 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
BuildEntity entity = (BuildEntity)target;
TileEntity core = getClosestCore();
if(isBuilding() && entity == null && isRebuild()){
if(isBuilding() && entity == null && canRebuild()){
target = world.tile(buildRequest().x, buildRequest().y);
circle(placeDistance * 0.7f);
target = null;
@@ -100,9 +96,9 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
incDrones(playerTarget);
TargetTrait prev = target;
target = playerTarget;
float dst = 90f + (id % 4)*30;
float dst = 90f + (id % 10)*3;
float tdst = dst(target);
float scale = (Mathf.lerp(1f, 0.77f, 1f - Mathf.clamp((tdst - dst) / dst)));
float scale = (Mathf.lerp(1f, 0.2f, 1f - Mathf.clamp((tdst - dst) / dst)));
circle(dst);
velocity.scl(scale);
target = prev;
@@ -151,9 +147,8 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
}
}
boolean isRebuild(){
//disabled until further notice, reason being that it's too annoying when playing enemies and too broken for ally use
return false; //Vars.state.rules.enemyCheat && team == waveTeam;
boolean canRebuild(){
return true;
}
@Override
@@ -188,13 +183,14 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
}
}
if(isRebuild() && !isBuilding()){
if(timer.get(timerTarget, 80) && Units.closestEnemy(getTeam(), x, y, 100f, u -> !(u instanceof BaseDrone)) == null && !isBuilding()){
TeamData data = Vars.state.teams.get(team);
if(!data.brokenBlocks.isEmpty()){
long block = data.brokenBlocks.removeLast();
placeQueue.addFirst(new BuildRequest(BrokenBlock.x(block), BrokenBlock.y(block), BrokenBlock.rotation(block), content.block(BrokenBlock.block(block))));
setState(build);
BrokenBlock block = data.brokenBlocks.removeLast();
if(Build.validPlace(getTeam(), block.x, block.y, content.block(block.block), block.rotation)){
placeQueue.addFirst(new BuildRequest(block.x, block.y, block.rotation, content.block(block.block)).configure(block.config));
setState(build);
}
}
}
}

View File

@@ -1,6 +1,4 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Crawler extends GroundUnit{
}

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Dagger extends GroundUnit{
}

View File

@@ -1,6 +1,4 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Eruptor extends GroundUnit{
}

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.type;
package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
@@ -8,6 +8,7 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
@@ -15,7 +16,7 @@ import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
public abstract class FlyingUnit extends BaseUnit{
public class FlyingUnit extends BaseUnit{
protected float[] weaponAngles = {0,0};
protected final UnitState
@@ -36,6 +37,10 @@ public abstract class FlyingUnit extends BaseUnit{
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
if(target == null && isCommanded() && getCommand() != UnitCommand.attack){
onCommand(getCommand());
}
}
if(getClosestSpawner() == null && getSpawner() != null && target == null){

View File

@@ -1,6 +1,4 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Fortress extends GroundUnit{
}

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Ghoul extends FlyingUnit{
}

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.type;
package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
@@ -9,6 +9,7 @@ import io.anuke.mindustry.*;
import io.anuke.mindustry.ai.Pathfinder.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
@@ -18,7 +19,7 @@ import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
public abstract class GroundUnit extends BaseUnit{
public class GroundUnit extends BaseUnit{
protected static Vector2 vec = new Vector2();
protected float walkTime;

View File

@@ -4,7 +4,6 @@ import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Revenant extends FlyingUnit{

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Titan extends GroundUnit{
}

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Wraith extends FlyingUnit{
}

View File

@@ -0,0 +1,23 @@
package io.anuke.mindustry.game;
import java.util.*;
/** Defines sizes of a content's preview icon. */
public enum Cicon{
/** Full size. */
full(0),
tiny(8 * 2),
small(8 * 3),
medium(8 * 4),
large(8 * 5),
xlarge(8 * 6);
public static final Cicon[] all = values();
public static final Cicon[] scaled = Arrays.copyOfRange(all, 1, all.length);
public final int size;
Cicon(int size){
this.size = size;
}
}

View File

@@ -1,12 +1,16 @@
package io.anuke.mindustry.game;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.ContentType;
/** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */
public abstract class Content{
public final short id;
/** The mod that loaded this piece of content. */
public @Nullable LoadedMod mod;
public Content(){
this.id = (short)Vars.content.getBy(getContentType()).size;

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.type.*;
@@ -83,6 +83,10 @@ public class EventType{
}
public static class ContentReloadEvent{
}
public static class DisposeEvent{
}
@@ -195,7 +199,8 @@ public class EventType{
public static class BlockBuildEndEvent{
public final Tile tile;
public final Team team;
public final @Nullable Player player;
public final @Nullable
Player player;
public final boolean breaking;
public BlockBuildEndEvent(Tile tile, @Nullable Player player, Team team, boolean breaking){

View File

@@ -150,6 +150,7 @@ public class GlobalData{
@SuppressWarnings("unchecked")
public void load(){
items.clear();
unlocked = Core.settings.getObject("unlocks", ObjectMap.class, ObjectMap::new);
for(Item item : Vars.content.items()){
items.put(item, Core.settings.getInt("item-" + item.name, 0));

View File

@@ -1,10 +1,13 @@
package io.anuke.mindustry.game;
import io.anuke.mindustry.*;
public abstract class MappableContent extends Content{
public final String name;
public MappableContent(String name){
this.name = name;
Vars.content.handleMappableContent(this);
}
@Override

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.audio.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
@@ -22,7 +22,8 @@ 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;

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.async.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.io.*;
@@ -253,6 +254,17 @@ public class Saves{
return meta.map;
}
public void cautiousLoad(Runnable run){
Array<String> mods = Array.with(getMods());
mods.removeAll(Vars.mods.getModStrings());
if(!mods.isEmpty()){
ui.showConfirm("$warning", Core.bundle.format("mod.missing", mods.toString("\n")), run);
}else{
run.run();
}
}
public String getName(){
return Core.settings.getString("save-" + index + "-name", "untitled");
}
@@ -262,6 +274,10 @@ public class Saves{
Core.settings.save();
}
public String[] getMods(){
return meta.mods;
}
public Zone getZone(){
return meta == null || meta.rules == null ? null : meta.rules.zone;
}

View File

@@ -1,9 +1,8 @@
package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.Struct;
import io.anuke.arc.collection.*;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.*;
import io.anuke.mindustry.world.*;
/** Class for various team-based utilities. */
public class Teams{
@@ -52,7 +51,7 @@ public class Teams{
public final ObjectSet<Tile> cores = new ObjectSet<>();
public final EnumSet<Team> enemies;
public final Team team;
public LongQueue brokenBlocks = new LongQueue();
public Queue<BrokenBlock> brokenBlocks = new Queue<>();
public TeamData(Team team, EnumSet<Team> enemies){
this.team = team;
@@ -62,8 +61,16 @@ public class Teams{
/** Represents a block made by this team that was destroyed somewhere on the map.
* This does not include deconstructed blocks.*/
@Struct
public class BrokenBlockStruct{
public short x, y, rotation, block;
public static class BrokenBlock{
public final short x, y, rotation, block;
public final int config;
public BrokenBlock(short x, short y, short rotation, short block, int config){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.config = config;
}
}
}

View File

@@ -1,9 +1,10 @@
package io.anuke.mindustry.game;
import io.anuke.arc.Core;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.Vars;
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.*;
/** Base interface for an unlockable content type. */
public abstract class UnlockableContent extends MappableContent{
@@ -11,6 +12,8 @@ public abstract class UnlockableContent extends MappableContent{
public String localizedName;
/** Localized description. May be null. */
public String description;
/** Icons by Cicon ID.*/
protected TextureRegion[] cicons = new TextureRegion[Cicon.all.length];
public UnlockableContent(String name){
super(name);
@@ -19,10 +22,24 @@ public abstract class UnlockableContent extends MappableContent{
this.description = Core.bundle.getOrNull(getContentType() + "." + name + ".description");
}
/** Generate any special icons for this content. Called asynchronously.*/
@CallSuper
public void createIcons(PixmapPacker out, PixmapPacker editor){
}
/** 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))));
}
return cicons[icon.ordinal()];
}
/** Returns the localized name of this content. */
public abstract String localizedName();
public abstract TextureRegion getContentIcon();
//public abstract TextureRegion getContentIcon();
/** This should show all necessary info about this content in the specified table. */
public abstract void displayInfo(Table table);

View File

@@ -2,15 +2,19 @@ package io.anuke.mindustry.graphics;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.Texture.TextureFilter;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.glutils.FrameBuffer;
import io.anuke.arc.graphics.glutils.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.base.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.Block.*;
import static io.anuke.arc.Core.camera;
import static io.anuke.mindustry.Vars.*;
@@ -120,6 +124,20 @@ public class BlockRenderer implements Disposable{
Draw.shader();
}
public void drawBroken(){
if(unitGroups[player.getTeam().ordinal()].all().contains(p -> p instanceof BuilderDrone)){
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.rect(b.icon(Cicon.full), block.x * tilesize + b.offset(), block.y * tilesize + b.offset(), b.rotate ? block.rotation * 90 : 0f);
}
Draw.reset();
}
}
public void drawShadows(){
if(!shadowEvents.isEmpty()){
Draw.flush();

View File

@@ -14,6 +14,7 @@ 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.world.*;
import io.anuke.mindustry.world.blocks.Floor;
@@ -252,7 +253,9 @@ public class MenuRenderer implements Disposable{
private void drawFlyers(){
Draw.color(0f, 0f, 0f, 0.4f);
float size = Math.max(flyerType.iconRegion.getWidth(), flyerType.iconRegion.getHeight()) * Draw.scl * 1.6f;
TextureRegion icon = flyerType.icon(Cicon.full);
float size = Math.max(icon.getWidth(), icon.getHeight()) * Draw.scl * 1.6f;
flyers((x, y) -> {
Draw.rect(flyerType.region, x - 12f, y - 13f, flyerRot - 90);

View File

@@ -106,7 +106,7 @@ public class MinimapRenderer implements Disposable{
public void updateAll(){
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, colorFor(world.tile(x, y)));
pixmap.draw(x, pixmap.getHeight() - 1 - y, colorFor(world.tile(x, y)));
}
}
texture.draw(pixmap, 0, 0);
@@ -114,7 +114,7 @@ public class MinimapRenderer implements Disposable{
public void update(Tile tile){
int color = colorFor(world.tile(tile.x, tile.y));
pixmap.drawPixel(tile.x, pixmap.getHeight() - 1 - tile.y, color);
pixmap.draw(tile.x, pixmap.getHeight() - 1 - tile.y, color);
Pixmaps.drawPixel(texture, tile.x, pixmap.getHeight() - 1 - tile.y, color);
}

View File

@@ -1,22 +1,18 @@
package io.anuke.mindustry.graphics;
import io.anuke.arc.Core;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Lines;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.input.InputHandler;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Tile;
import io.anuke.arc.*;
import io.anuke.arc.graphics.*;
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.mindustry.*;
import io.anuke.mindustry.content.*;
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.world.*;
import static io.anuke.mindustry.Vars.*;
@@ -112,6 +108,13 @@ public class OverlayRenderer{
if(tile != null && tile.block() != Blocks.air && tile.getTeam() == player.getTeam()){
tile.block().drawSelect(tile);
if(Core.input.keyDown(Binding.rotateplaced) && tile.block().rotate){
control.input.drawArrow(tile.block(), tile.x, tile.y, tile.rotation(), true);
Draw.color(Pal.accent, 0.3f + Mathf.absin(4f, 0.2f));
Fill.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize/2f);
Draw.color();
}
}
}
@@ -119,7 +122,7 @@ public class OverlayRenderer{
if(input.isDroppingItem()){
Vector2 v = Core.input.mouseWorld(input.getMouseX(), input.getMouseY());
float size = 8;
Draw.rect(player.item().item.icon(Item.Icon.large), v.x, v.y, size, size);
Draw.rect(player.item().item.icon(Cicon.medium), v.x, v.y, size, size);
Draw.color(Pal.accent);
Lines.circle(v.x, v.y, 6 + Mathf.absin(Time.time(), 5f, 1f));
Draw.reset();

View File

@@ -1,17 +1,18 @@
package io.anuke.mindustry.graphics;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.Core;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.graphics.glutils.Shader;
import io.anuke.arc.scene.ui.layout.Scl;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.Time;
public class Shaders{
public static Shadow shadow;
public static BlockBuild blockbuild;
public static @Nullable Shield shield;
public static @Nullable
Shield shield;
public static UnitBuild build;
public static FogShader fog;
public static MenuShader menu;

View File

@@ -13,6 +13,7 @@ public enum Binding implements KeyBind{
deselect(KeyCode.MOUSE_RIGHT),
break_block(KeyCode.MOUSE_RIGHT),
rotate(new Axis(KeyCode.SCROLL)),
rotateplaced(KeyCode.R),
diagonal_placement(KeyCode.CONTROL_LEFT),
pick(KeyCode.MOUSE_MIDDLE),
dash(KeyCode.SHIFT_LEFT),

View File

@@ -11,9 +11,9 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.PlaceUtils.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.*;
import static io.anuke.arc.Core.scene;
@@ -189,6 +189,10 @@ public class DesktopInput extends InputHandler{
if(canTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y)){
cursorType = ui.unloadCursor;
}
if(!isPlacing() && Math.abs(Core.input.axisTap(Binding.rotate)) > 0 && Core.input.keyDown(Binding.rotateplaced) && cursor.block().rotate){
Call.rotateBlock(player, cursor, Core.input.axisTap(Binding.rotate) > 0);
}
}
if(!Core.scene.hasMouse()){

View File

@@ -57,6 +57,20 @@ public abstract class InputHandler implements InputProcessor{
player.clearItem();
}
@Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true)
public static void rotateBlock(Player player, Tile tile, boolean direction){
if(net.server() && !Units.canInteract(player, tile)){
throw new ValidateException(player, "Player cannot drop an item.");
}
tile.rotation(Mathf.mod(tile.rotation() + Mathf.sign(direction), 4));
if(tile.entity != null){
tile.entity.updateProximity();
tile.entity.noSleep();
}
}
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Tile tile){
if(player == null || player.timer == null || !player.timer.get(Player.timerTransfer, 40)) return;
@@ -114,6 +128,12 @@ public abstract class InputHandler implements InputProcessor{
tile.block().tapped(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);
}
public OverlayFragment getFrag(){
return frag;
}
@@ -352,15 +372,19 @@ public abstract class InputHandler implements InputProcessor{
player.addBuildRequest(new BuildRequest(tile.x, tile.y));
}
void drawArrow(Block block, int x, int y, int rotation){
Draw.color(!validPlace(x, y, block, rotation) ? Pal.removeBack : Pal.accentBack);
public void drawArrow(Block block, int x, int y, int rotation){
drawArrow(block, x, y, rotation, validPlace(x, y, block, rotation));
}
public void drawArrow(Block block, int x, int y, int rotation, boolean valid){
Draw.color(!valid ? Pal.removeBack : Pal.accentBack);
Draw.rect(Core.atlas.find("place-arrow"),
x * tilesize + block.offset(),
y * tilesize + block.offset() - 1,
Core.atlas.find("place-arrow").getWidth() * Draw.scl,
Core.atlas.find("place-arrow").getHeight() * Draw.scl, rotation * 90 - 90);
Draw.color(!validPlace(x, y, block, rotation) ? Pal.remove : Pal.accent);
Draw.color(!valid ? Pal.remove : Pal.accent);
Draw.rect(Core.atlas.find("place-arrow"),
x * tilesize + block.offset(),
y * tilesize + block.offset(),

View File

@@ -1,12 +1,10 @@
package io.anuke.mindustry.io;
import io.anuke.arc.collection.*;
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.game.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@@ -98,6 +96,7 @@ public class JsonIO{
}
});
/*
json.setSerializer(TeamData.class, new Serializer<TeamData>(){
@Override
public void write(Json json, TeamData object, Class knownType){
@@ -115,7 +114,7 @@ public class JsonIO{
out.brokenBlocks = new LongQueue(blocks);
return out;
}
});
});*/
json.setSerializer(ItemStack.class, new Serializer<ItemStack>(){
@Override

View File

@@ -81,8 +81,8 @@ public class MapIO{
super.setBlock(type);
int c = colorFor(Blocks.air, block(), Blocks.air, getTeam());
if(c != black){
walls.drawPixel(x, floors.getHeight() - 1 - y, c);
floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade);
walls.draw(x, floors.getHeight() - 1 - y, c);
floors.draw(x, floors.getHeight() - 1 - y + 1, shade);
}
}
@@ -112,9 +112,9 @@ public class MapIO{
@Override
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
if(overlayID != 0){
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(Blocks.air, Blocks.air, content.block(overlayID), Team.derelict));
floors.draw(x, floors.getHeight() - 1 - y, colorFor(Blocks.air, Blocks.air, content.block(overlayID), Team.derelict));
}else{
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.derelict));
floors.draw(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.derelict));
}
if(content.block(overlayID) == Blocks.spawn){
map.spawns ++;
@@ -136,7 +136,7 @@ public class MapIO{
for(int x = 0; x < pixmap.getWidth(); x++){
for(int y = 0; y < pixmap.getHeight(); y++){
Tile tile = tiles[x][y];
pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, colorFor(tile.floor(), tile.block(), tile.overlay(), tile.getTeam()));
pixmap.draw(x, pixmap.getHeight() - 1 - y, colorFor(tile.floor(), tile.block(), tile.overlay(), tile.getTeam()));
}
}
return pixmap;

View File

@@ -5,8 +5,7 @@ import io.anuke.arc.files.FileHandle;
import io.anuke.arc.util.io.CounterInputStream;
import io.anuke.arc.util.io.FastDeflaterOutputStream;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.io.versions.Save1;
import io.anuke.mindustry.io.versions.Save2;
import io.anuke.mindustry.io.versions.*;
import io.anuke.mindustry.world.WorldContext;
import java.io.*;
@@ -19,7 +18,7 @@ public class SaveIO{
/** Format header. This is the string 'MSAV' in ASCII. */
public static final byte[] header = {77, 83, 65, 86};
public static final IntMap<SaveVersion> versions = new IntMap<>();
public static final Array<SaveVersion> versionArray = Array.with(new Save1(), new Save2());
public static final Array<SaveVersion> versionArray = Array.with(new Save1(), new Save2(), new Save3());
static{
for(SaveVersion version : versionArray){

View File

@@ -15,6 +15,7 @@ public class SaveMeta{
public int wave;
public Rules rules;
public StringMap tags;
public String[] mods;
public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, StringMap tags){
this.version = version;
@@ -25,5 +26,6 @@ public class SaveMeta{
this.wave = wave;
this.rules = rules;
this.tags = tags;
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
}
}

View File

@@ -6,7 +6,7 @@ import io.anuke.arc.util.io.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@@ -16,7 +16,7 @@ import java.io.*;
import static io.anuke.mindustry.Vars.*;
public abstract class SaveVersion extends SaveFileReader{
public final int version;
public int version;
//HACK stores the last read build of the save file, valid after read meta call
protected int lastReadBuild;
@@ -66,6 +66,7 @@ public abstract class SaveVersion extends SaveFileReader{
"wavetime", state.wavetime,
"stats", JsonIO.write(state.stats),
"rules", JsonIO.write(state.rules),
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
"width", world.width(),
"height", world.height()
).merge(tags));
@@ -80,6 +81,7 @@ public abstract class SaveVersion extends SaveFileReader{
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
lastReadBuild = map.getInt("build", -1);
String[] mods = JsonIO.read(String[].class, map.get("mods", "[]"));
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
world.setMap(worldmap == null ? new Map(StringMap.of(
@@ -206,6 +208,21 @@ public abstract class SaveVersion extends SaveFileReader{
}
public void writeEntities(DataOutput stream) throws IOException{
//write team data with entities.
Array<TeamData> data = state.teams.getActive();
stream.writeInt(data.size);
for(TeamData team : data){
stream.writeInt(team.team.ordinal());
stream.writeInt(team.brokenBlocks.size);
for(BrokenBlock block : team.brokenBlocks){
stream.writeShort(block.x);
stream.writeShort(block.y);
stream.writeShort(block.rotation);
stream.writeShort(block.block);
stream.writeInt(block.config);
}
}
//write entity chunk
int groups = 0;
@@ -234,6 +251,16 @@ public abstract class SaveVersion extends SaveFileReader{
}
public void readEntities(DataInput stream) throws IOException{
int teamc = stream.readInt();
for(int i = 0; i < teamc; i++){
Team team = Team.all[stream.readInt()];
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()));
}
}
byte groups = stream.readByte();
for(int i = 0; i < groups; i++){
@@ -290,17 +317,4 @@ public abstract class SaveVersion extends SaveFileReader{
}
}
}
/** sometimes it's necessary to remap IDs after the content header is read.*/
public void remapContent(){
for(Team team : Team.all){
if(state.teams.isActive(team)){
LongQueue queue = state.teams.get(team).brokenBlocks;
for(int i = 0; i < queue.size; i++){
//remap broken block IDs
queue.set(i, BrokenBlock.block(queue.get(i), content.block(BrokenBlock.block(queue.get(i))).id));
}
}
}
}
}

View File

@@ -118,7 +118,7 @@ public class LegacyTypeTable{
public static Supplier[] getTable(int build){
if(build == -1 || build == 81){
//return most recent one since that's probably is; not guaranteed
//return most recent one since that's probably it; not guaranteed
return build81Table;
}else if(build == 80){
return build80Table;

View File

@@ -1,16 +1,14 @@
package io.anuke.mindustry.io.versions;
import io.anuke.arc.function.Supplier;
import io.anuke.mindustry.entities.traits.SaveTrait;
import io.anuke.mindustry.io.SaveVersion;
import io.anuke.arc.function.*;
import io.anuke.mindustry.entities.traits.*;
import java.io.DataInput;
import java.io.IOException;
import java.io.*;
public class Save1 extends SaveVersion{
public class Save1 extends Save2{
public Save1(){
super(1);
version = 1;
}
@Override

View File

@@ -1,9 +1,35 @@
package io.anuke.mindustry.io.versions;
import io.anuke.mindustry.io.SaveVersion;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import java.io.*;
import static io.anuke.mindustry.Vars.content;
public class Save2 extends SaveVersion{
public Save2(){
super(2);
}
@Override
public void readEntities(DataInput stream) throws IOException{
byte groups = stream.readByte();
for(int i = 0; i < groups; i++){
int amount = stream.readInt();
for(int j = 0; j < amount; j++){
//TODO throw exception on read fail
readChunk(stream, true, in -> {
byte typeid = in.readByte();
byte version = in.readByte();
SaveTrait trait = (SaveTrait)content.<TypeID>getByID(ContentType.typeid, typeid).constructor.get();
trait.readSave(in, version);
});
}
}
}
}

View File

@@ -0,0 +1,9 @@
package io.anuke.mindustry.io.versions;
import io.anuke.mindustry.io.*;
public class Save3 extends SaveVersion{
public Save3(){
super(3);
}
}

View File

@@ -70,7 +70,7 @@ public class Maps{
* Does not add this map to the map list.
*/
public Map loadInternalMap(String name){
FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension);
FileHandle file = tree.get("maps/" + name + "." + mapExtension);
try{
return MapIO.createMap(file, false);

View File

@@ -8,20 +8,20 @@ import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.Block.*;
import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.updateEditorOnChange;
public abstract class FilterOption{
public static final Predicate<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Icon.full));
public static final Predicate<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Icon.full));
public static final Predicate<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Icon.full)));
public static final Predicate<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Icon.full)));
public static final Predicate<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Icon.full)));
public static final Predicate<Block> oresOnly = b -> b instanceof OverlayFloor && Core.atlas.isFound(b.icon(Icon.full));
public static final Predicate<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Cicon.full));
public static final Predicate<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Cicon.full));
public static final Predicate<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Cicon.full)));
public static final Predicate<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Cicon.full)));
public static final Predicate<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(Cicon.full)));
public static final Predicate<Block> oresOnly = b -> b instanceof OverlayFloor && Core.atlas.isFound(b.icon(Cicon.full));
public static final Predicate<Block> anyOptional = b -> floorsOnly.test(b) || wallsOnly.test(b) || oresOnly.test(b) || b == Blocks.air;
public abstract void build(Table table);
@@ -76,15 +76,15 @@ public abstract class FilterOption{
@Override
public void build(Table table){
table.addButton(b -> b.addImage(supplier.get().icon(Icon.small)).update(i -> ((TextureRegionDrawable)i.getDrawable())
.setRegion(supplier.get() == Blocks.air ? Core.atlas.find("icon-none") : supplier.get().icon(Icon.small))).size(8 * 3), () -> {
table.addButton(b -> b.addImage(supplier.get().icon(Cicon.small)).update(i -> ((TextureRegionDrawable)i.getDrawable())
.setRegion(supplier.get() == Blocks.air ? Core.atlas.find("icon-none") : supplier.get().icon(Cicon.small))).size(8 * 3), () -> {
FloatingDialog dialog = new FloatingDialog("");
dialog.setFillParent(false);
int i = 0;
for(Block block : Vars.content.blocks()){
if(!filter.test(block)) continue;
dialog.cont.addImage(block == Blocks.air ? Core.atlas.find("icon-none-small") : block.icon(Icon.medium)).size(8 * 4).pad(3).get().clicked(() -> {
dialog.cont.addImage(block == Blocks.air ? Core.atlas.find("icon-none-small") : block.icon(Cicon.medium)).size(8 * 4).pad(3).get().clicked(() -> {
consumer.accept(block);
dialog.hide();
changed.run();

View File

@@ -0,0 +1,345 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.*;
import io.anuke.arc.audio.*;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.reflect.Field;
import io.anuke.arc.util.reflect.*;
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.entities.Effects.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import java.lang.reflect.*;
@SuppressWarnings("unchecked")
public class ContentParser{
private static final boolean ignoreUnknownFields = true;
private ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
private ObjectMap<Class<?>, FieldParser> classParsers = new ObjectMap<Class<?>, FieldParser>(){{
put(BulletType.class, (type, data) -> field(Bullets.class, data));
put(Effect.class, (type, data) -> field(Fx.class, data));
put(StatusEffect.class, (type, data) -> field(StatusEffects.class, data));
put(Loadout.class, (type, data) -> field(Loadouts.class, data));
put(Color.class, (type, data) -> Color.valueOf(data.asString()));
put(Music.class, (type, data) -> {
if(fieldOpt(Musics.class, data) != null) return fieldOpt(Musics.class, data);
String path = "music/" + data.asString() + (Vars.ios ? ".mp3" : ".ogg");
Core.assets.load(path, Music.class);
Core.assets.finishLoadingAsset(path);
return Core.assets.get(path);
});
put(Sound.class, (type, data) -> {
if(fieldOpt(Sounds.class, data) != null) return fieldOpt(Sounds.class, data);
String path = "sounds/" + data.asString() + (Vars.ios ? ".mp3" : ".ogg");
Core.assets.load(path, Sound.class);
Core.assets.finishLoadingAsset(path);
Log.info(Core.assets.get(path));
return Core.assets.get(path);
});
}};
/** Stores things that need to be parsed fully, e.g. reading fields of content.
* This is done to accomodate binding of content names first.*/
private Array<Runnable> reads = new Array<>();
private LoadedMod currentMod;
private Json parser = new Json(){
public <T> T readValue(Class<T> type, Class elementType, JsonValue jsonData){
if(type != null){
if(classParsers.containsKey(type)){
return (T)classParsers.get(type).parse(type, jsonData);
}
if(Content.class.isAssignableFrom(type)){
ContentType ctype = contentTypes.getThrow(type, () -> new IllegalArgumentException("No content type for class: " + type.getSimpleName()));
String prefix = currentMod != null ? currentMod.name + "-" : "";
T one = (T)Vars.content.getByName(ctype, prefix + jsonData.asString());
if(one != null) return one;
return (T)Vars.content.getByName(ctype, jsonData.asString());
}
}
return super.readValue(type, elementType, jsonData);
}
};
private ObjectMap<ContentType, TypeParser<?>> parsers = ObjectMap.of(
ContentType.block, (TypeParser<Block>)(mod, name, value) -> {
//TODO generate dynamically instead of doing.. this
Class<? extends Block> type = resolve(value.getString("type"),
"io.anuke.mindustry.world",
"io.anuke.mindustry.world.blocks",
"io.anuke.mindustry.world.blocks.defense",
"io.anuke.mindustry.world.blocks.defense.turrets",
"io.anuke.mindustry.world.blocks.distribution",
"io.anuke.mindustry.world.blocks.logic",
"io.anuke.mindustry.world.blocks.power",
"io.anuke.mindustry.world.blocks.production",
"io.anuke.mindustry.world.blocks.sandbox",
"io.anuke.mindustry.world.blocks.storage",
"io.anuke.mindustry.world.blocks.units"
);
Block block = type.getDeclaredConstructor(String.class).newInstance(mod + "-" + name);
read(() -> {
if(value.has("consumes")){
for(JsonValue child : value.get("consumes")){
if(child.name.equals("item")){
if(child.isString()){
block.consumes.item(Vars.content.getByName(ContentType.item, child.asString()));
}else{
ItemStack stack = parser.readValue(ItemStack.class, child);
block.consumes.item(stack.item, stack.amount);
}
}else if(child.name.equals("items")){
block.consumes.items(parser.readValue(ItemStack[].class, child));
}else if(child.name.equals("liquid")){
LiquidStack stack = parser.readValue(LiquidStack.class, child);
block.consumes.liquid(stack.liquid, stack.amount);
}else if(child.name.equals("power")){
block.consumes.power(child.asFloat());
}else if(child.name.equals("powerBuffered")){
block.consumes.powerBuffered(child.asFloat());
}else{
throw new IllegalArgumentException("Unknown consumption type: '" + child.name + "' for block '" + block.name + "'.");
}
}
value.remove("consumes");
}
readFields(block, value, true);
//add research tech node
if(value.has("research")){
TechTree.create(Vars.content.getByName(ContentType.block, value.get("research").asString()), block);
}
//make block visible
if(value.has("requirements")){
block.buildVisibility = () -> true;
}
});
return block;
},
ContentType.unit, (TypeParser<UnitType>)(mod, name, value) -> {
Class<BaseUnit> type = resolve(value.getString("type"), "io.anuke.mindustry.entities.type.base");
UnitType unit = new UnitType(mod + "-" + name, supply(type));
read(() -> readFields(unit, value, true));
return unit;
},
ContentType.item, parser(ContentType.item, Item::new),
ContentType.liquid, parser(ContentType.liquid, Liquid::new),
ContentType.mech, parser(ContentType.mech, Mech::new),
ContentType.zone, parser(ContentType.zone, Zone::new)
);
private <T extends Content> TypeParser<T> parser(ContentType type, Function<String, T> constructor){
return (mod, name, value) -> {
T item;
if(Vars.content.getByName(type, name) != null){
item = (T)Vars.content.getByName(type, name);
}else{
item = constructor.get(mod + "-" + name);
}
read(() -> readFields(item, value));
return item;
};
}
/** Call to read a content's extra info later.*/
private void read(Runnable run){
LoadedMod mod = currentMod;
reads.add(() -> {
this.currentMod = mod;
run.run();
});
}
private void init(){
for(ContentType type : ContentType.all){
Array<Content> arr = Vars.content.getBy(type);
if(!arr.isEmpty()){
Class<?> c = arr.first().getClass();
//get base content class, skipping intermediates
while(!(c.getSuperclass() == Content.class || c.getSuperclass() == UnlockableContent.class || Modifier.isAbstract(c.getSuperclass().getModifiers()))){
c = c.getSuperclass();
}
contentTypes.put(c, type);
}
}
}
public void finishParsing(){
reads.each(Runnable::run);
reads.clear();
}
/**
* Parses content from a json file.
* @param name the name of the file without its extension
* @param json the json to parse
* @param type the type of content this is
* @return the content that was parsed
*/
public Content parse(LoadedMod mod, String name, String json, ContentType type) throws Exception{
if(contentTypes.isEmpty()){
init();
}
JsonValue value = parser.fromJson(null, json);
if(!parsers.containsKey(type)){
throw new SerializationException("No parsers for content type '" + type + "'");
}
currentMod = mod;
Content c = parsers.get(type).parse(mod.name, name, value);
c.mod = mod;
checkNulls(c);
return c;
}
private <T> Supplier<T> supply(Class<T> type){
try{
java.lang.reflect.Constructor<T> cons = type.getDeclaredConstructor();
return () -> {
try{
return cons.newInstance();
}catch(Exception e){
throw new RuntimeException(e);
}
};
}catch(Exception e){
throw new RuntimeException(e);
}
}
private Object field(Class<?> type, JsonValue value){
return field(type, value.asString());
}
/** Gets a field from a static class by name, throwing a descriptive exception if not found. */
private Object field(Class<?> type, String name){
try{
Object b = type.getField(name).get(null);
if(b == null) throw new IllegalArgumentException(type.getSimpleName() + ": not found: '" + name + "'");
return b;
}catch(Exception e){
throw new RuntimeException(e);
}
}
private Object fieldOpt(Class<?> type, JsonValue value){
try{
Object b = type.getField(value.asString()).get(null);
if(b == null) return null;
return b;
}catch(Exception e){
return null;
}
}
/** Checks all @NonNull fields in this object, recursively.
* Throws an exception if any are null.*/
private void checkNulls(Object object){
checkNulls(object, new ObjectSet<>());
}
private void checkNulls(Object object, ObjectSet<Object> checked){
checked.add(object);
parser.getFields(object.getClass()).values().toArray().each(field -> {
try{
if(field.field.getType().isPrimitive()) return;
Object obj = field.field.get(object);
if(field.field.isAnnotationPresent(NonNull.class) && field.field.get(object) == null){
throw new RuntimeException("Field '" + field.field.getName() + "' in " + object.getClass().getSimpleName() + " is missing!");
}
if(obj != null && !checked.contains(obj)){
checkNulls(obj, checked);
checked.add(obj);
}
}catch(Exception e){
throw new RuntimeException(e);
}
});
}
private void readFields(Object object, JsonValue jsonMap, boolean stripType){
if(stripType) jsonMap.remove("type");
readFields(object, jsonMap);
}
private void readFields(Object object, JsonValue jsonMap){
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = parser.getFields(type);
for(JsonValue child = jsonMap.child; child != null; child = child.next){
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
if(metadata == null){
if(ignoreUnknownFields){
if(!child.name.equals("research")){
Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object);
}
continue;
}else{
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
ex.addTrace(child.trace());
throw ex;
}
}
Field field = metadata.field;
try{
field.set(object, parser.readValue(field.getType(), metadata.elementType, child));
}catch(ReflectionException ex){
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex);
}catch(SerializationException ex){
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}catch(RuntimeException runtimeEx){
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(child.trace());
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}
}
}
/** Tries to resolve a class from a list of potential class names. */
private <T> Class<T> resolve(String base, String... potentials) throws Exception{
for(String type : potentials){
try{
return (Class<T>)Class.forName(type + '.' + base);
}catch(Exception ignored){
}
}
throw new IllegalArgumentException("Type not found: " + potentials[0]);
}
private interface FieldParser{
Object parse(Class<?> type, JsonValue value);
}
private interface TypeParser<T extends Content>{
T parse(String mod, String name, JsonValue value) throws Exception;
}
}

View File

@@ -0,0 +1,32 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.files.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
public class Mod{
/** @return the config file for this plugin, as the file 'mods/[plugin-name]/config.json'.*/
public FileHandle getConfig(){
return Vars.mods.getConfig(this);
}
/** Called after all plugins have been created and commands have been registered.*/
public void init(){
}
/** Create any content needed here. */
public void loadContent(){
}
/** Register any commands to be used on the server side, e.g. from the console. */
public void registerServerCommands(CommandHandler handler){
}
/** Register any commands to be used on the client side, e.g. sent from an in-game player.. */
public void registerClientCommands(CommandHandler handler){
}
}

View File

@@ -0,0 +1,419 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Pixmap.*;
import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.g2d.TextureAtlas.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.type.*;
import java.io.*;
import java.net.*;
import static io.anuke.mindustry.Vars.*;
public class Mods implements Loadable{
private Json json = new Json();
private ContentParser parser = new ContentParser();
private ObjectMap<String, Array<FileHandle>> bundles = new ObjectMap<>();
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites");
private int totalSprites;
private PixmapPacker packer;
private Array<LoadedMod> loaded = new Array<>();
private Array<LoadedMod> disabled = new Array<>();
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
private boolean requiresReload;
/** Returns a file named 'config.json' in a special folder for the specified plugin.
* Call this in init(). */
public FileHandle getConfig(Mod mod){
ModMeta load = metas.get(mod.getClass());
if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!");
return modDirectory.child(load.name).child("config.json");
}
/** @return the loaded mod found by class, or null if not found. */
public @Nullable
LoadedMod getMod(Class<? extends Mod> type){
return loaded.find(l -> l.mod.getClass() == type);
}
/** Imports an external mod file.*/
public void importMod(FileHandle file) throws IOException{
FileHandle dest = modDirectory.child(file.name());
if(dest.exists()){
throw new IOException("A mod with the same filename already exists!");
}
file.copyTo(dest);
try{
loaded.add(loadMod(file));
requiresReload = true;
}catch(IOException e){
dest.delete();
throw e;
}catch(Throwable t){
dest.delete();
throw new IOException(t);
}
}
/** Repacks all in-game sprites. */
@Override
public void loadAsync(){
if(loaded.isEmpty()) return;
packer = new PixmapPacker(2048, 2048, Format.RGBA8888, 2, true);
for(LoadedMod mod : loaded){
int[] packed = {0};
boolean[] failed = {false};
mod.root.child("sprites").walk(file -> {
if(failed[0]) return;
if(file.extension().equals("png")){
try(InputStream stream = file.read()){
byte[] bytes = Streams.copyStreamToByteArray(stream, Math.max((int)file.length(), 512));
Pixmap pixmap = new Pixmap(bytes, 0, bytes.length);
packer.pack(mod.name + "-" + file.nameWithoutExtension(), pixmap);
pixmap.dispose();
packed[0] ++;
totalSprites ++;
}catch(IOException e){
failed[0] = true;
Core.app.post(() -> {
Log.err("Error packing images for mod: {0}", mod.meta.name);
e.printStackTrace();
if(!headless) ui.showException(e);
});
}
}
});
Log.info("Packed {0} images for mod '{1}'.", packed[0], mod.meta.name);
}
}
@Override
public void loadSync(){
if(packer == null) return;
Texture editor = Core.atlas.find("clear-editor").getTexture();
PixmapPacker editorPacker = new PixmapPacker(2048, 2048, Format.RGBA8888, 2, true);
for(AtlasRegion region : Core.atlas.getRegions()){
if(region.getTexture() == editor){
editorPacker.pack(region.name, Core.atlas.getPixmap(region).crop());
}
}
//get textures packed
if(totalSprites > 0){
TextureFilter filter = Core.settings.getBool("linear") ? TextureFilter.Linear : TextureFilter.Nearest;
packer.updateTextureAtlas(Core.atlas, filter, filter, false);
//generate new icons
for(Array<Content> arr : content.getContentMap()){
arr.each(c -> {
if(c instanceof UnlockableContent && c.mod != null){
UnlockableContent u = (UnlockableContent)c;
u.createIcons(packer, editorPacker);
}
});
}
editorPacker.updateTextureAtlas(Core.atlas, filter, filter, false);
packer.updateTextureAtlas(Core.atlas, filter, filter, false);
}
packer.dispose();
packer = null;
}
/** Removes a mod file and marks it for requiring a restart. */
public void removeMod(LoadedMod mod){
if(mod.file.isDirectory()){
mod.file.deleteDirectory();
}else{
mod.file.delete();
}
loaded.remove(mod);
requiresReload = true;
}
public boolean requiresReload(){
return requiresReload;
}
/** Loads all mods from the folder, but does call any methods on them.*/
public void load(){
for(FileHandle file : modDirectory.list()){
if(!file.extension().equals("jar") && !file.extension().equals("zip") && !(file.isDirectory() && file.child("mod.json").exists())) continue;
try{
LoadedMod mod = loadMod(file);
if(mod.enabled()){
loaded.add(mod);
}else{
disabled.add(mod);
}
}catch(IllegalArgumentException ignored){
}catch(Exception e){
Log.err("Failed to load plugin file {0}. Skipping.", file);
e.printStackTrace();
}
}
//sort mods to make sure servers handle them properly.
loaded.sort(Structs.comparing(m -> m.name));
buildFiles();
}
private void buildFiles(){
for(LoadedMod mod : loaded){
for(FileHandle file : mod.root.list()){
//ignore special folders like bundles or sprites
if(file.isDirectory() && !specialFolders.contains(file.name())){
//TODO calling child/parent on these files will give you gibberish; create wrapper class.
file.walk(f -> tree.addFile(mod.file.isDirectory() ? f.path().substring(1 + mod.file.path().length()) : f.path(), f));
}
}
//load up bundles.
FileHandle folder = mod.root.child("bundles");
if(folder.exists()){
for(FileHandle file : folder.list()){
if(file.name().startsWith("bundle") && file.extension().equals("properties")){
String name = file.nameWithoutExtension();
bundles.getOr(name, Array::new).add(file);
}
}
}
}
//add new keys to each bundle
I18NBundle bundle = Core.bundle;
while(bundle != null){
String str = bundle.getLocale().toString();
String locale = "bundle" + (str.isEmpty() ? "" : "_" + str);
for(FileHandle file : bundles.getOr(locale, Array::new)){
try{
PropertiesUtils.load(bundle.getProperties(), file.reader());
}catch(Exception e){
throw new RuntimeException("Error loading bundle: " + file + "/" + locale, e);
}
}
bundle = bundle.getParent();
}
}
/** Reloads all mod content. How does this even work? I refuse to believe that it functions correctly.*/
public void reloadContent(){
//epic memory leak
Core.atlas = new TextureAtlas(Core.files.internal("sprites/sprites.atlas"));
loaded.clear();
disabled.clear();
load();
buildFiles();
Musics.dispose();
Sounds.dispose();
Musics.load();
Sounds.load();
Core.assets.finishLoading();
content.clear();
content.createContent();
loadAsync();
loadSync();
content.init();
content.load();
content.loadColors();
data.load();
requiresReload = false;
}
/** Creates all the content found in mod files. */
public void loadContent(){
for(LoadedMod mod : loaded){
if(mod.root.child("content").exists()){
FileHandle contentRoot = mod.root.child("content");
for(ContentType type : ContentType.all){
FileHandle folder = contentRoot.child(type.name().toLowerCase() + "s");
if(folder.exists()){
for(FileHandle file : folder.list()){
if(file.extension().equals("json")){
try{
//this binds the content but does not load it entirely
Content loaded = parser.parse(mod, file.nameWithoutExtension(), file.readString(), type);
Log.info("[{0}] Loaded '{1}'.", mod.meta.name, loaded);
}catch(Exception e){
throw new RuntimeException("Failed to parse content file '" + file + "' for mod '" + mod.meta.name + "'.", e);
}
}
}
}
}
}
}
//this finishes parsing content fields
parser.finishParsing();
each(Mod::loadContent);
}
/** @return all loaded mods. */
public Array<LoadedMod> all(){
return loaded;
}
/** @return all disabled mods. */
public Array<LoadedMod> disabled(){
return disabled;
}
/** @return a list of mod names only, without versions. */
public Array<String> getModNames(){
return loaded.select(l -> !l.meta.hidden).map(l -> l.name + ":" + l.meta.version);
}
/** @return a list of mods and versions, in the format name:version. */
public Array<String> getModStrings(){
return loaded.select(l -> !l.meta.hidden).map(l -> l.name + ":" + l.meta.version);
}
/** Makes a mod enabled or disabled. shifts it.*/
public void setEnabled(LoadedMod mod, boolean enabled){
if(mod.enabled() != enabled){
Core.settings.putSave(mod.name + "-enabled", enabled);
requiresReload = true;
if(!enabled){
loaded.remove(mod);
disabled.add(mod);
}else{
loaded.add(mod);
disabled.remove(mod);
}
}
}
/** @return the mods that the client is missing.
* The inputted array is changed to contain the extra mods that the client has but the server doesn't.*/
public Array<String> getIncompatibility(Array<String> out){
Array<String> mods = getModStrings();
Array<String> result = mods.copy();
for(String mod : mods){
if(out.remove(mod)){
result.remove(mod);
}
}
return result;
}
/** Iterates through each mod with a main class.*/
public void each(Consumer<Mod> cons){
loaded.each(p -> p.mod != null, p -> cons.accept(p.mod));
}
/** Loads a mod file+meta, but does not add it to the list.
* Note that directories can be loaded as mods.*/
private LoadedMod loadMod(FileHandle sourceFile) throws Exception{
FileHandle zip = sourceFile.isDirectory() ? sourceFile : new ZipFileHandle(sourceFile);
if(zip.list().length == 1 && zip.list()[0].isDirectory()){
zip = zip.list()[0];
}
FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("plugin.json");
if(!metaf.exists()){
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", sourceFile);
throw new IllegalArgumentException("No mod.json found.");
}
ModMeta meta = json.fromJson(ModMeta.class, metaf.readString());
String camelized = meta.name.replace(" ", "");
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
String baseName = meta.name.toLowerCase().replace(" ", "-");
if(loaded.contains(m -> m.name.equals(baseName)) || disabled.contains(m -> m.name.equals(baseName))){
throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported.");
}
Mod mainMod;
FileHandle mainFile = zip;
String[] path = (mainClass.replace('.', '/') + ".class").split("/");
for(String str : path){
if(!str.isEmpty()){
mainFile = mainFile.child(str);
}
}
//make sure the main class exists before loading it; if it doesn't just don't put it there
if(mainFile.exists()){
//other platforms don't have standard java class loaders
if(!headless && Version.build != -1){
throw new IllegalArgumentException("Java class mods are currently unsupported outside of custom builds.");
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{sourceFile.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(mainClass);
metas.put(main, meta);
mainMod = (Mod)main.getDeclaredConstructor().newInstance();
}else{
mainMod = null;
}
//all plugins are hidden implicitly
if(mainMod instanceof Plugin){
meta.hidden = true;
}
return new LoadedMod(sourceFile, zip, mainMod, meta);
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedMod{
/** The location of this mod's zip file/folder on the disk. */
public final FileHandle file;
/** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */
public final FileHandle root;
/** The mod's main class; may be null. */
public final @Nullable Mod mod;
/** Internal mod name. Used for textures. */
public final String name;
/** This mod's metadata. */
public final ModMeta meta;
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){
this.root = root;
this.file = file;
this.mod = mod;
this.meta = meta;
this.name = meta.name.toLowerCase().replace(" ", "-");
}
public boolean enabled(){
return Core.settings.getBool(name + "-enabled", true);
}
}
/** Plugin metadata information.*/
public static class ModMeta{
public String name, author, description, version, main;
public String[] dependencies = {}; //TODO implement
/** Hidden mods are only server-side or client-side, and do not support adding new content. */
public boolean hidden;
}
}

View File

@@ -26,7 +26,7 @@ public class CrashSender{
exception.printStackTrace();
//don't create crash logs for custom builds, as it's expected
if(Version.build == -1) return;
if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return;
//attempt to load version regardless
if(Version.number == 0){
@@ -93,6 +93,7 @@ public class CrashSender{
ex(() -> value.addChild("versionNumber", new JsonValue(Version.number)));
ex(() -> value.addChild("versionModifier", new JsonValue(Version.modifier)));
ex(() -> value.addChild("build", new JsonValue(Version.build)));
ex(() -> value.addChild("revision", new JsonValue(Version.revision)));
ex(() -> value.addChild("net", new JsonValue(fn)));
ex(() -> value.addChild("server", new JsonValue(fs)));
ex(() -> value.addChild("players", new JsonValue(Vars.playerGroup.size())));
@@ -143,8 +144,7 @@ public class CrashSender{
private static void ex(Runnable r){
try{
r.run();
}catch(Throwable t){
t.printStackTrace();
}catch(Throwable ignored){
}
}
}

View File

@@ -1,10 +1,10 @@
package io.anuke.mindustry.net;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Packets.*;
@@ -21,7 +21,8 @@ public class Net{
private boolean server;
private boolean active;
private boolean clientLoaded;
private @Nullable StreamBuilder currentStream;
private @Nullable
StreamBuilder currentStream;
private final Array<Object> packetQueue = new Array<>();
private final ObjectMap<Class<?>, Consumer> clientListeners = new ObjectMap<>();

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.net;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Administration.*;
@@ -15,7 +15,8 @@ import static io.anuke.mindustry.Vars.netServer;
public abstract class NetConnection{
public final String address;
public boolean mobile, modclient;
public @Nullable Player player;
public @Nullable
Player player;
/** ID of last recieved client snapshot. */
public int lastRecievedClientSnapshot = -1;
@@ -48,7 +49,7 @@ public abstract class NetConnection{
/** Kick with an arbitrary reason. */
public void kick(String reason){
Log.info("Kicking connection {0}; Reason: {1}", address, reason);
Log.info("Kicking connection {0}; Reason: {1}", address, reason.replace("\n", " "));
if(player != null && player.uuid != null){
PlayerInfo info = netServer.admins.getInfo(player.uuid);

View File

@@ -1,6 +1,7 @@
package io.anuke.mindustry.net;
import io.anuke.arc.Core;
import io.anuke.arc.collection.*;
import io.anuke.arc.util.serialization.Base64Coder;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.io.TypeIO;
@@ -65,6 +66,7 @@ public class Packets{
public static class ConnectPacket implements Packet{
public int version;
public String versionType;
public Array<String> mods;
public String name, uuid, usid;
public boolean mobile;
public int color;
@@ -78,6 +80,10 @@ public class Packets{
buffer.put(mobile ? (byte)1 : 0);
buffer.putInt(color);
buffer.put(Base64Coder.decode(uuid));
buffer.putInt(mods.size);
for(int i = 0; i < mods.size; i++){
TypeIO.writeString(buffer, mods.get(i));
}
}
@Override
@@ -91,6 +97,11 @@ public class Packets{
byte[] idbytes = new byte[8];
buffer.get(idbytes);
uuid = new String(Base64Coder.encode(idbytes));
int totalMods = buffer.getInt();
mods = new Array<>(totalMods);
for(int i = 0; i < totalMods; i++){
mods.add(TypeIO.readString(buffer));
}
}
}

View File

@@ -1,28 +1,7 @@
package io.anuke.mindustry.plugin;
import io.anuke.arc.files.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.mod.*;
public abstract class Plugin{
public abstract class Plugin extends Mod{
/** @return the config file for this plugin, as the file 'plugins/[plugin-name]/config.json'.*/
public FileHandle getConfig(){
return Vars.plugins.getConfig(this);
}
/** Called after all plugins have been created and commands have been registered.*/
public void init(){
}
/** Register any commands to be used on the server side, e.g. from the console. */
public void registerServerCommands(CommandHandler handler){
}
/** Register any commands to be used on the client side, e.g. sent from an in-game player.. */
public void registerClientCommands(CommandHandler handler){
}
}

View File

@@ -1,93 +0,0 @@
package io.anuke.mindustry.plugin;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.io.*;
import java.net.*;
import static io.anuke.mindustry.Vars.pluginDirectory;
public class Plugins{
private Array<LoadedPlugin> loaded = new Array<>();
private ObjectMap<Class<?>, PluginMeta> metas = new ObjectMap<>();
/** Returns a file named 'config.json' in a special folder for the specified plugin.
* Call this in init(). */
public FileHandle getConfig(Plugin plugin){
PluginMeta load = metas.get(plugin.getClass());
if(load == null) throw new IllegalArgumentException("Plugin is not loaded yet (or missing)!");
return pluginDirectory.child(load.name).child("config.json");
}
/** @return the loaded plugin found by class, or null if not found. */
public @Nullable LoadedPlugin getPlugin(Class<? extends Plugin> type){
return loaded.find(l -> l.plugin.getClass() == type);
}
/** Loads all plugins from the folder, but does call any methods on them.*/
public void load(){
for(FileHandle file : pluginDirectory.list()){
if(!file.extension().equals("jar")) continue;
try{
loaded.add(loadPlugin(file));
}catch(IllegalArgumentException ignored){
}catch(Exception e){
Log.err("Failed to load plugin file {0}. Skipping.", file);
e.printStackTrace();
}
}
}
/** @return all loaded plugins. */
public Array<LoadedPlugin> all(){
return loaded;
}
/** Iterates through each plugin.*/
public void each(Consumer<Plugin> cons){
loaded.each(p -> cons.accept(p.plugin));
}
private LoadedPlugin loadPlugin(FileHandle jar) throws Exception{
FileHandle zip = new ZipFileHandle(jar);
FileHandle metaf = zip.child("plugin.json");
if(!metaf.exists()){
Log.warn("Plugin {0} doesn't have a 'plugin.json' file, skipping.", jar);
throw new IllegalArgumentException();
}
PluginMeta meta = JsonIO.read(PluginMeta.class, metaf.readString());
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(meta.main);
metas.put(main, meta);
return new LoadedPlugin(jar, zip, (Plugin)main.getDeclaredConstructor().newInstance(), meta);
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedPlugin{
public final FileHandle jarFile;
public final FileHandle zipRoot;
public final Plugin plugin;
public final PluginMeta meta;
public LoadedPlugin(FileHandle jarFile, FileHandle zipRoot, Plugin plugin, PluginMeta meta){
this.zipRoot = zipRoot;
this.jarFile = jarFile;
this.plugin = plugin;
this.meta = meta;
}
}
/** Plugin metadata information.*/
public static class PluginMeta{
public String name, author, main, description;
public String version;
}
}

View File

@@ -13,5 +13,7 @@ public enum ContentType{
effect,
zone,
loadout,
typeid
typeid;
public static final ContentType[] all = values();
}

View File

@@ -3,7 +3,6 @@ package io.anuke.mindustry.type;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.ui.*;
@@ -13,7 +12,6 @@ import static io.anuke.mindustry.Vars.content;
public class Item extends UnlockableContent implements Comparable<Item>{
public final Color color;
private TextureRegion[] regions;
/** type of the item; used for tabs and core acceptance. default value is {@link ItemType#resource}. */
public ItemType type = ItemType.resource;
@@ -39,17 +37,8 @@ public class Item extends UnlockableContent implements Comparable<Item>{
this.description = Core.bundle.getOrNull("item." + this.name + ".description");
}
@Override
public void load(){
regions = new TextureRegion[Icon.values().length];
for(int i = 0; i < regions.length; i++){
Icon icon = Icon.values()[i];
regions[i] = Core.atlas.find(icon == Icon.large ? "item-" + name : "item-" + name + "-" + icon.name());
}
}
public TextureRegion icon(Icon icon){
return regions[icon.ordinal()];
public Item(String name){
this(name, new Color(Color.black));
}
@Override
@@ -67,11 +56,6 @@ public class Item extends UnlockableContent implements Comparable<Item>{
return Core.bundle.get("item." + this.name + ".name");
}
@Override
public TextureRegion getContentIcon(){
return icon(Icon.large);
}
@Override
public String toString(){
return localizedName();
@@ -87,20 +71,6 @@ public class Item extends UnlockableContent implements Comparable<Item>{
return ContentType.item;
}
public enum Icon{
small(8 * 2),
medium(8 * 3),
large(8 * 4),
xlarge(8 * 5),
xxlarge(8 * 6);
public final int size;
Icon(int size){
this.size = size;
}
}
/** Allocates a new array containing all items that generate ores. */
public static Array<Item> getAllOres(){
return content.blocks().select(b -> b instanceof OreBlock).map(b -> ((Floor)b).itemDrop);

View File

@@ -5,7 +5,7 @@ import io.anuke.mindustry.content.Items;
public class ItemStack implements Comparable<ItemStack>{
public Item item;
public int amount;
public int amount = 1;
public ItemStack(Item item, int amount){
if(item == null) item = Items.copper;

View File

@@ -1,12 +1,11 @@
package io.anuke.mindustry.type;
import io.anuke.arc.Core;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.game.UnlockableContent;
import io.anuke.mindustry.ui.ContentDisplay;
import io.anuke.arc.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.ui.*;
public class Liquid extends UnlockableContent{
public final Color color;
@@ -25,8 +24,6 @@ public class Liquid extends UnlockableContent{
public Color flameColor = Color.valueOf("ffb763");
/** The associated status effect. */
public StatusEffect effect = StatusEffects.none;
/** Displayed icon. TODO fix it by removing autogen, draw icons manually */
public TextureRegion iconRegion;
public Liquid(String name, Color color){
super(name);
@@ -34,13 +31,13 @@ public class Liquid extends UnlockableContent{
this.description = Core.bundle.getOrNull("liquid." + name + ".description");
}
public boolean canExtinguish(){
return flammability < 0.1f && temperature <= 0.5f;
/** For modding only.*/
public Liquid(String name){
this(name, new Color(Color.black));
}
@Override
public void load(){
iconRegion = Core.atlas.find("liquid-" + name);
public boolean canExtinguish(){
return flammability < 0.1f && temperature <= 0.5f;
}
@Override
@@ -53,11 +50,6 @@ public class Liquid extends UnlockableContent{
return Core.bundle.get("liquid." + this.name + ".name");
}
@Override
public TextureRegion getContentIcon(){
return iconRegion;
}
@Override
public String toString(){
return localizedName();

View File

@@ -9,6 +9,11 @@ public class LiquidStack{
this.amount = amount;
}
/** serialization only*/
protected LiquidStack(){
}
@Override
public String toString(){
return "LiquidStack{" +

View File

@@ -33,7 +33,7 @@ public class Mech extends UnlockableContent{
public float weaponOffsetX, weaponOffsetY, engineOffset = 5f, engineSize = 2.5f;
public Weapon weapon;
public TextureRegion baseRegion, legRegion, region, iconRegion;
public TextureRegion baseRegion, legRegion, region;
public Mech(String name, boolean flying){
super(name);
@@ -41,6 +41,10 @@ public class Mech extends UnlockableContent{
this.description = Core.bundle.get("mech." + name + ".description");
}
public Mech(String name){
this(name, false);
}
public String localizedName(){
return Core.bundle.get("mech." + name + ".name");
}
@@ -90,11 +94,6 @@ public class Mech extends UnlockableContent{
ContentDisplay.displayMech(table, this);
}
@Override
public TextureRegion getContentIcon(){
return iconRegion;
}
@Override
public ContentType getContentType(){
return ContentType.mech;
@@ -109,7 +108,6 @@ public class Mech extends UnlockableContent{
}
region = Core.atlas.find(name);
iconRegion = Core.atlas.find("mech-icon-" + name);
}
@Override

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
@@ -13,8 +14,8 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.ui.*;
public class UnitType extends UnlockableContent{
public final TypeID typeID;
public final Supplier<? extends BaseUnit> constructor;
public @NonNull TypeID typeID;
public @NonNull Supplier<? extends BaseUnit> constructor;
public float health = 60;
public float hitsize = 7f;
@@ -25,7 +26,7 @@ public class UnitType extends UnlockableContent{
public float baseRotateSpeed = 0.1f;
public float shootCone = 15f;
public float mass = 1f;
public boolean isFlying;
public boolean flying;
public boolean targetAir = true;
public boolean rotateWeapon = false;
public float drag = 0.1f;
@@ -34,15 +35,24 @@ public class UnitType extends UnlockableContent{
public int itemCapacity = 30;
public ObjectSet<Item> toMine = ObjectSet.with(Items.lead, Items.copper);
public float buildPower = 0.3f, minePower = 0.7f;
public Weapon weapon;
public @NonNull Weapon weapon;
public float weaponOffsetY, engineOffset = 6f, engineSize = 2f;
public ObjectSet<StatusEffect> immunities = new ObjectSet<>();
public Sound deathSound = Sounds.bang;
public TextureRegion iconRegion, legRegion, baseRegion, region;
public TextureRegion legRegion, baseRegion, region;
public <T extends BaseUnit> UnitType(String name, Class<T> type, Supplier<T> mainConstructor){
public <T extends BaseUnit> UnitType(String name, Supplier<T> mainConstructor){
this(name);
create(mainConstructor);
}
public <T extends BaseUnit> UnitType(String name){
super(name);
this.description = Core.bundle.getOrNull("unit." + name + ".description");
}
public <T extends BaseUnit> void create(Supplier<T> mainConstructor){
this.constructor = mainConstructor;
this.description = Core.bundle.getOrNull("unit." + name + ".description");
this.typeID = new TypeID(name, mainConstructor);
@@ -58,15 +68,9 @@ public class UnitType extends UnlockableContent{
return Core.bundle.get("unit." + name + ".name");
}
@Override
public TextureRegion getContentIcon(){
return iconRegion;
}
@Override
public void load(){
weapon.load();
iconRegion = Core.atlas.find("unit-icon-" + name, Core.atlas.find(name));
region = Core.atlas.find(name);
legRegion = Core.atlas.find(name + "-leg");
baseRegion = Core.atlas.find(name + "-base");

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.audio.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
@@ -25,7 +26,7 @@ public class Weapon{
protected static float minPlayerDist = 20f;
protected static int sequenceNum = 0;
/** bullet shot */
public BulletType bullet;
public @NonNull BulletType bullet;
/** shell ejection effect */
public Effect ejectEffect = Fx.none;
/** weapon reload in frames */

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
@@ -17,7 +18,7 @@ import java.util.*;
import static io.anuke.mindustry.Vars.*;
public class Zone extends UnlockableContent{
public final Generator generator;
public @NonNull Generator generator;
public Block[] blockRequirements = {};
public ZoneRequirement[] zoneRequirements = {};
public Item[] resources = {};
@@ -40,9 +41,13 @@ public class Zone extends UnlockableContent{
this.generator = generator;
}
public Zone(String name){
this(name, new MapGenerator(name));
}
@Override
public void load(){
preview = Core.atlas.find("zone-" + name);
preview = Core.atlas.find("zone-" + name, Core.atlas.find(name + "-zone"));
}
public Rules getRules(){
@@ -193,11 +198,6 @@ public class Zone extends UnlockableContent{
public void displayInfo(Table table){
}
@Override
public TextureRegion getContentIcon(){
return null;
}
@Override
public String localizedName(){
return Core.bundle.get("zone." + name + ".name");
@@ -209,14 +209,18 @@ public class Zone extends UnlockableContent{
}
public static class ZoneRequirement{
public final Zone zone;
public final int wave;
public @NonNull Zone zone;
public @NonNull int wave;
public ZoneRequirement(Zone zone, int wave){
this.zone = zone;
this.wave = wave;
}
protected ZoneRequirement(){
}
public static ZoneRequirement[] with(Object... objects){
ZoneRequirement[] out = new ZoneRequirement[objects.length / 2];
for(int i = 0; i < objects.length; i += 2){

View File

@@ -41,6 +41,10 @@ public class Bar extends Element{
}
public void reset(float value){
this.value = lastValue = blink = value;
}
public void set(Supplier<String> name, FloatProvider fraction, Color color){
this.fraction = fraction;
this.lastValue = fraction.get();

View File

@@ -1,15 +1,14 @@
package io.anuke.mindustry.ui;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.OrderedMap;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Strings;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Block.Icon;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.meta.*;
public class ContentDisplay{
@@ -19,7 +18,7 @@ public class ContentDisplay{
table.table(title -> {
int size = 8 * 6;
title.addImage(block.icon(Icon.large)).size(size);
title.addImage(block.icon(Cicon.xlarge)).size(size);
title.add("[accent]" + block.localizedName).padLeft(5);
});
@@ -67,7 +66,7 @@ public class ContentDisplay{
public static void displayItem(Table table, Item item){
table.table(title -> {
title.addImage(item.getContentIcon()).size(8 * 6);
title.addImage(item.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + item.localizedName()).padLeft(5);
});
@@ -98,7 +97,7 @@ public class ContentDisplay{
public static void displayLiquid(Table table, Liquid liquid){
table.table(title -> {
title.addImage(liquid.getContentIcon()).size(8 * 6);
title.addImage(liquid.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + liquid.localizedName()).padLeft(5);
});
@@ -132,7 +131,7 @@ public class ContentDisplay{
public static void displayMech(Table table, Mech mech){
table.table(title -> {
title.addImage(mech.getContentIcon()).size(8 * 6);
title.addImage(mech.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + mech.localizedName()).padLeft(5);
});
table.left().defaults().left();
@@ -180,7 +179,7 @@ public class ContentDisplay{
public static void displayUnit(Table table, UnitType unit){
table.table(title -> {
title.addImage(unit.getContentIcon()).size(8 * 6);
title.addImage(unit.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + unit.localizedName()).padLeft(5);
});

View File

@@ -1,11 +1,10 @@
package io.anuke.mindustry.ui;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.scene.ui.Image;
import io.anuke.arc.scene.ui.layout.Stack;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.type.Item.Icon;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
public class ItemImage extends Stack{
@@ -25,7 +24,7 @@ public class ItemImage extends Stack{
}
public ItemImage(ItemStack stack){
add(new Image(stack.item.icon(Icon.large)));
add(new Image(stack.item.icon(Cicon.medium)));
if(stack.amount != 0){
Table t = new Table().left().bottom();

View File

@@ -1,22 +1,17 @@
package io.anuke.mindustry.ui;
import io.anuke.arc.collection.ObjectIntMap;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.Item.Icon;
import io.anuke.mindustry.type.ItemType;
import io.anuke.mindustry.type.*;
import java.text.NumberFormat;
import java.util.Locale;
import static io.anuke.mindustry.Vars.content;
import static io.anuke.mindustry.Vars.data;
import static io.anuke.mindustry.Vars.*;
/** Displays a list of items, e.g. launched items.*/
public class ItemsDisplay extends Table{
private static final NumberFormat format = NumberFormat.getNumberInstance(Locale.getDefault());
private StringBuilder builder = new StringBuilder();
public ItemsDisplay(){
rebuild();
@@ -29,17 +24,26 @@ public class ItemsDisplay extends Table{
table(Tex.button,t -> {
t.margin(10).marginLeft(15).marginTop(15f);
t.add("$launcheditems").colspan(3).left().padBottom(5);
t.label(() -> state.is(State.menu) ? "$launcheditems" : "$launchinfo").colspan(3).padBottom(4).left().colspan(3).width(210f).wrap();
t.row();
ObjectIntMap<Item> items = data.items();
for(Item item : content.items()){
if(item.type == ItemType.material && data.isUnlocked(item)){
t.label(() -> format.format(items.get(item, 0))).left();
t.addImage(item.icon(Icon.medium)).size(8 * 3).padLeft(4).padRight(4);
t.label(() -> format(item)).left();
t.addImage(item.icon(Cicon.small)).size(8 * 3).padLeft(4).padRight(4);
t.add(item.localizedName()).color(Color.lightGray).left();
t.row();
}
}
});
}
private String format(Item item){
builder.setLength(0);
builder.append(ui.formatAmount(data.items().get(item, 0)));
if(!state.is(State.menu) && !state.teams.get(player.getTeam()).cores.isEmpty() && state.teams.get(player.getTeam()).cores.first().entity != null && state.teams.get(player.getTeam()).cores.first().entity.items.get(item) > 0){
builder.append(" [unlaunched]+ ");
builder.append(ui.formatAmount(state.teams.get(player.getTeam()).cores.first().entity.items.get(item)));
}
return builder.toString();
}
}

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.scene.ui.Image;
import io.anuke.arc.scene.ui.layout.Stack;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Strings;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.Liquid;
import io.anuke.mindustry.world.meta.StatUnit;
@@ -20,7 +21,7 @@ public class LiquidDisplay extends Table{
this.perSecond = perSecond;
add(new Stack(){{
add(new Image(liquid.getContentIcon()));
add(new Image(liquid.icon(Cicon.medium)));
if(amount != 0){
Table t = new Table().left().bottom();

View File

@@ -91,8 +91,8 @@ public class Minimap extends Table{
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(e != null && e.isDescendantOf(this)){
Core.scene.setScrollFocus(this);
}else if(Core.scene.getScrollFocus() == this){
requestScroll();
}else if(hasScroll()){
Core.scene.setScrollFocus(null);
}
});

View File

@@ -42,6 +42,8 @@ public class CustomRulesDialog extends FloatingDialog{
main.addButton("$settings.reset", () -> {
rules = resetter.get();
setup();
requestKeyboard();
requestScroll();
}).size(300f, 50f);
main.left().defaults().fillX().left().pad(5);
main.row();

View File

@@ -10,8 +10,7 @@ import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.game.Content;
import io.anuke.mindustry.game.UnlockableContent;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.type.ContentType;
@@ -56,7 +55,7 @@ public class DatabaseDialog extends FloatingDialog{
for(int i = 0; i < array.size; i++){
UnlockableContent unlock = (UnlockableContent)array.get(i);
Image image = unlocked(unlock) ? new Image(unlock.getContentIcon()) : new Image(Icon.lockedSmall, Pal.gray);
Image image = unlocked(unlock) ? new Image(unlock.icon(Cicon.medium)) : new Image(Icon.lockedSmall, Pal.gray);
list.add(image).size(8*4).pad(3);
ClickListener listener = new ClickListener();
image.addListener(listener);

View File

@@ -103,19 +103,21 @@ public class DeployDialog extends FloatingDialog{
}
TextButton button = Elements.newButton(Core.bundle.format("resume", slot.getZone().localizedName()), Styles.squaret, () -> {
hide();
ui.loadAnd(() -> {
logic.reset();
net.reset();
try{
control.saves.getZoneSlot().load();
state.set(State.playing);
}catch(SaveException e){ //make sure to handle any save load errors!
e.printStackTrace();
if(control.saves.getZoneSlot() != null) control.saves.getZoneSlot().delete();
Core.app.post(() -> ui.showInfo("$save.corrupted"));
show();
}
control.saves.getZoneSlot().cautiousLoad(() -> {
hide();
ui.loadAnd(() -> {
logic.reset();
net.reset();
try{
slot.load();
state.set(State.playing);
}catch(SaveException e){ //make sure to handle any save load errors!
e.printStackTrace();
if(control.saves.getZoneSlot() != null) control.saves.getZoneSlot().delete();
Core.app.post(() -> ui.showInfo("$save.corrupted"));
show();
}
});
});
});

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