Merge branches 'lights' and 'master' of https://github.com/Anuken/Mindustry into lights
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -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"){{
|
||||
|
||||
@@ -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){
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
34
core/src/io/anuke/mindustry/core/FileTree.java
Normal file
34
core/src/io/anuke/mindustry/core/FileTree.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(){}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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[]";
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
package io.anuke.mindustry.entities.type.base;
|
||||
|
||||
import io.anuke.mindustry.entities.type.GroundUnit;
|
||||
|
||||
public class Crawler extends GroundUnit{
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package io.anuke.mindustry.entities.type.base;
|
||||
|
||||
import io.anuke.mindustry.entities.type.GroundUnit;
|
||||
|
||||
public class Dagger extends GroundUnit{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
package io.anuke.mindustry.entities.type.base;
|
||||
|
||||
import io.anuke.mindustry.entities.type.GroundUnit;
|
||||
|
||||
public class Eruptor extends GroundUnit{
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
@@ -1,6 +1,4 @@
|
||||
package io.anuke.mindustry.entities.type.base;
|
||||
|
||||
import io.anuke.mindustry.entities.type.GroundUnit;
|
||||
|
||||
public class Fortress extends GroundUnit{
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package io.anuke.mindustry.entities.type.base;
|
||||
|
||||
import io.anuke.mindustry.entities.type.FlyingUnit;
|
||||
|
||||
public class Ghoul extends FlyingUnit{
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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{
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package io.anuke.mindustry.entities.type.base;
|
||||
|
||||
import io.anuke.mindustry.entities.type.GroundUnit;
|
||||
|
||||
public class Titan extends GroundUnit{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package io.anuke.mindustry.entities.type.base;
|
||||
|
||||
import io.anuke.mindustry.entities.type.FlyingUnit;
|
||||
|
||||
public class Wraith extends FlyingUnit{
|
||||
|
||||
}
|
||||
|
||||
23
core/src/io/anuke/mindustry/game/Cicon.java
Normal file
23
core/src/io/anuke/mindustry/game/Cicon.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()){
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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", "[]"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
core/src/io/anuke/mindustry/io/versions/Save3.java
Normal file
9
core/src/io/anuke/mindustry/io/versions/Save3.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
345
core/src/io/anuke/mindustry/mod/ContentParser.java
Normal file
345
core/src/io/anuke/mindustry/mod/ContentParser.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
32
core/src/io/anuke/mindustry/mod/Mod.java
Normal file
32
core/src/io/anuke/mindustry/mod/Mod.java
Normal 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){
|
||||
|
||||
}
|
||||
}
|
||||
419
core/src/io/anuke/mindustry/mod/Mods.java
Normal file
419
core/src/io/anuke/mindustry/mod/Mods.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,7 @@ public enum ContentType{
|
||||
effect,
|
||||
zone,
|
||||
loadout,
|
||||
typeid
|
||||
typeid;
|
||||
|
||||
public static final ContentType[] all = values();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -9,6 +9,11 @@ public class LiquidStack{
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
/** serialization only*/
|
||||
protected LiquidStack(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "LiquidStack{" +
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user