This commit is contained in:
Anuken
2019-08-30 16:39:40 -04:00
172 changed files with 6795 additions and 5599 deletions

View File

@@ -0,0 +1,177 @@
package io.anuke.mindustry;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.maps.*;
import static io.anuke.arc.Core.*;
import static io.anuke.mindustry.Vars.*;
public class ClientLauncher extends ApplicationCore{
private static final int loadingFPS = 20;
private float smoothProgress;
private long lastTime;
private long beginTime;
private boolean finished = false;
@Override
public void setup(){
Log.setUseColors(false);
beginTime = Time.millis();
Time.setDeltaProvider(() -> {
float result = Core.graphics.getDeltaTime() * 60f;
return (Float.isNaN(result) || Float.isInfinite(result)) ? 1f : Mathf.clamp(result, 0.0001f, 60f / 10f);
});
batch = new SpriteBatch();
assets = new AssetManager();
assets.setLoader(Texture.class, "." + mapExtension, new MapPreviewLoader());
atlas = TextureAtlas.blankAtlas();
UI.loadDefaultFont();
UI.loadSystemCursors();
//1. bundles
//2. rest of vars
assets.load(new Vars());
assets.load(new AssetDescriptor<>("sprites/sprites.atlas", TextureAtlas.class)).loaded = t -> atlas = (TextureAtlas)t;
assets.loadRun("maps", Map.class, () -> {
maps.loadPreviews();
});
Musics.load();
Sounds.load();
assets.loadRun("contentcreate", Content.class, () -> {
content.createContent();
content.loadColors();
});
add(logic = new Logic());
add(control = new Control());
add(renderer = new Renderer());
add(ui = new UI());
add(netServer = new NetServer());
add(netClient = new NetClient());
assets.loadRun("contentinit", ContentLoader.class, () -> {
content.init();
content.load();
});
}
@Override
public void add(ApplicationListener module){
super.add(module);
//autoload modules when necessary
if(module instanceof Loadable){
assets.load((Loadable)module);
}
}
@Override
public void resize(int width, int height){
super.resize(width, height);
if(!assets.isFinished()){
Draw.proj().setOrtho(0, 0, width, height);
}
}
@Override
public void update(){
if(!finished){
drawLoading();
if(assets.update(1000 / loadingFPS)){
Log.info("Total time to load: {0}", Time.timeSinceMillis(beginTime));
for(ApplicationListener listener : modules){
listener.init();
}
finished = true;
Events.fire(new ClientLoadEvent());
}
}else{
super.update();
}
int targetfps = Core.settings.getInt("fpscap", 120);
if(targetfps > 0 && targetfps <= 240){
long target = (1000 * 1000000) / targetfps; //target in nanos
long elapsed = Time.timeSinceNanos(lastTime);
if(elapsed < target){
try{
Thread.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000));
}catch(InterruptedException ignored){
//ignore
}
}
}
lastTime = Time.nanos();
}
@Override
public void init(){
setup();
}
@Override
public void resume(){
if(finished){
super.resume();
}
}
void drawLoading(){
smoothProgress = Mathf.lerpDelta(smoothProgress, assets.getProgress(), 0.1f);
Core.graphics.clear(Pal.darkerGray);
Draw.proj().setOrtho(0, 0, Core.graphics.getWidth(), Core.graphics.getHeight());
float height = UnitScl.dp.scl(50f);
Draw.color(Color.BLACK);
Fill.poly(graphics.getWidth()/2f, graphics.getHeight()/2f, 6, Mathf.dst(graphics.getWidth()/2f, graphics.getHeight()/2f) * smoothProgress);
Draw.reset();
float w = graphics.getWidth()*0.6f;
Draw.color(Color.BLACK);
Fill.rect(graphics.getWidth()/2f, graphics.getHeight()/2f, w, height);
Draw.color(Pal.accent);
Fill.crect(graphics.getWidth()/2f-w/2f, graphics.getHeight()/2f - height/2f, w * smoothProgress, height);
for(int i : Mathf.signs){
Fill.tri(graphics.getWidth()/2f + w/2f*i, graphics.getHeight()/2f + height/2f, graphics.getWidth()/2f + w/2f*i, graphics.getHeight()/2f - height/2f, graphics.getWidth()/2f + w/2f*i + height/2f*i, graphics.getHeight()/2f);
}
if(assets.isLoaded("outline")){
BitmapFont font = assets.get("outline");
font.draw((int)(assets.getProgress() * 100) + "%", graphics.getWidth() / 2f, graphics.getHeight() / 2f + UnitScl.dp.scl(10f), Align.center);
font.draw(bundle.get("loading", "").replace("[accent]", ""), graphics.getWidth() / 2f, graphics.getHeight() / 2f + height / 2f + UnitScl.dp.scl(20), Align.center);
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";
font.draw(bundle.get("load." + key, ""), graphics.getWidth() / 2f, graphics.getHeight() / 2f - height / 2f - UnitScl.dp.scl(10f), Align.center);
}
}
Draw.flush();
}
}

View File

@@ -1,72 +0,0 @@
package io.anuke.mindustry;
import io.anuke.arc.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.io.*;
import static io.anuke.mindustry.Vars.*;
public class Mindustry extends ApplicationCore{
private long lastTime;
@Override
public void setup(){
Time.setDeltaProvider(() -> {
float result = Core.graphics.getDeltaTime() * 60f;
return (Float.isNaN(result) || Float.isInfinite(result)) ? 1f : Mathf.clamp(result, 0.0001f, 60f / 10f);
});
Time.mark();
UI.loadSystemCursors();
Vars.init();
Log.setUseColors(false);
BundleLoader.load();
Musics.load();
Sounds.load();
content.load();
content.loadColors();
add(logic = new Logic());
add(world = new World());
add(control = new Control());
add(renderer = new Renderer());
add(ui = new UI());
add(netServer = new NetServer());
add(netClient = new NetClient());
}
@Override
public void update(){
super.update();
int targetfps = Core.settings.getInt("fpscap", 120);
if(targetfps > 0 && targetfps <= 240){
long target = (1000 * 1000000) / targetfps; //target in nanos
long elapsed = Time.timeSinceNanos(lastTime);
if(elapsed < target){
try{
Thread.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000));
}catch(InterruptedException ignored){
//ignore
}
}
}
lastTime = Time.nanos();
}
@Override
public void init(){
super.init();
Log.info("Time to load [total]: {0}", Time.elapsed());
Events.fire(new GameLoadEvent());
}
}

View File

@@ -1,35 +1,36 @@
package io.anuke.mindustry;
import io.anuke.arc.Application.ApplicationType;
import io.anuke.arc.Core;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.util.Structs;
import io.anuke.arc.Application.*;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.ai.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.effect.Fire;
import io.anuke.mindustry.entities.effect.Puddle;
import io.anuke.mindustry.entities.impl.EffectEntity;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.impl.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.Serialization;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.ShieldEntity;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Locale;
import java.nio.charset.*;
import java.util.*;
@SuppressWarnings("unchecked")
public class Vars{
public class Vars implements Loadable{
/** Whether to load locales.*/
public static boolean loadLocales = true;
/** IO buffer size. */
public static final int bufferSize = 8192;
/** global charset */
/** global charset, since Android doesn't support the Charsets class */
public static final Charset charset = Charset.forName("UTF-8");
/** main application name, capitalized */
public static final String appName = "Mindustry";
@@ -116,10 +117,14 @@ public class Vars{
public static FileHandle screenshotDirectory;
/** data subdirectory used for custom mmaps */
public static FileHandle customMapDirectory;
/** data subdirectory used for custom mmaps */
public static FileHandle mapPreviewDirectory;
/** tmp subdirectory for map conversion */
public static FileHandle tmpDirectory;
/** data subdirectory used for saves */
public static FileHandle saveDirectory;
/** data subdirectory used for plugins */
public static FileHandle pluginDirectory;
/** old map file extension, for conversion */
public static final String oldMapExtension = "mmap";
/** map file extension */
@@ -137,14 +142,20 @@ public class Vars{
public static DefaultWaves defaultWaves;
public static LoopControl loops;
public static World world;
public static Maps maps;
public static WaveSpawner spawner;
public static BlockIndexer indexer;
public static Pathfinder pathfinder;
public static Control control;
public static Logic logic;
public static Renderer renderer;
public static UI ui;
public static World world;
public static NetServer netServer;
public static NetClient netClient;
public static Entities entities;
public static EntityGroup<Player> playerGroup;
public static EntityGroup<TileEntity> tileGroup;
public static EntityGroup<Bullet> bulletGroup;
@@ -158,6 +169,12 @@ public class Vars{
/** all local players, currently only has one player. may be used for local co-op in the future */
public static Player player;
@Override
public void loadAsync(){
loadSettings();
init();
}
public static void init(){
Serialization.init();
@@ -180,29 +197,32 @@ public class Vars{
Version.init();
content = new ContentLoader();
if(!headless){
content.setVerbose();
}
loops = new LoopControl();
defaultWaves = new DefaultWaves();
collisions = new EntityCollisions();
world = new World();
playerGroup = Entities.addGroup(Player.class).enableMapping();
tileGroup = Entities.addGroup(TileEntity.class, false);
bulletGroup = Entities.addGroup(Bullet.class).enableMapping();
effectGroup = Entities.addGroup(EffectEntity.class, false);
groundEffectGroup = Entities.addGroup(DrawTrait.class, false);
puddleGroup = Entities.addGroup(Puddle.class).enableMapping();
shieldGroup = Entities.addGroup(ShieldEntity.class, false);
fireGroup = Entities.addGroup(Fire.class).enableMapping();
maps = new Maps();
spawner = new WaveSpawner();
indexer = new BlockIndexer();
pathfinder = new Pathfinder();
entities = new Entities();
playerGroup = entities.add(Player.class).enableMapping();
tileGroup = entities.add(TileEntity.class, false);
bulletGroup = entities.add(Bullet.class).enableMapping();
effectGroup = entities.add(EffectEntity.class, false);
groundEffectGroup = entities.add(DrawTrait.class, false);
puddleGroup = entities.add(Puddle.class).enableMapping();
shieldGroup = entities.add(ShieldEntity.class, false);
fireGroup = entities.add(Fire.class).enableMapping();
unitGroups = new EntityGroup[Team.all.length];
for(Team team : Team.all){
unitGroups[team.ordinal()] = Entities.addGroup(BaseUnit.class).enableMapping();
unitGroups[team.ordinal()] = entities.add(BaseUnit.class).enableMapping();
}
for(EntityGroup<?> group : Entities.getAllGroups()){
for(EntityGroup<?> group : entities.all()){
group.setRemoveListener(entity -> {
if(entity instanceof SyncTrait && Net.client()){
netClient.addRemovedEntity((entity).getID());
@@ -217,16 +237,64 @@ public class Vars{
ios = Core.app.getType() == ApplicationType.iOS;
android = Core.app.getType() == ApplicationType.Android;
dataDirectory = Core.settings.getDataDirectory();
screenshotDirectory = dataDirectory.child("screenshots/");
customMapDirectory = dataDirectory.child("maps/");
mapPreviewDirectory = dataDirectory.child("previews/");
saveDirectory = dataDirectory.child("saves/");
tmpDirectory = dataDirectory.child("tmp/");
pluginDirectory = dataDirectory.child("plugins/");
maps.load();
}
public static void loadSettings(){
Core.settings.setAppName(appName);
if(steam){
Core.settings.setDataDirectory(Core.files.local("saves/"));
}
dataDirectory = Core.settings.getDataDirectory();
screenshotDirectory = dataDirectory.child("screenshots/");
customMapDirectory = dataDirectory.child("maps/");
saveDirectory = dataDirectory.child("saves/");
tmpDirectory = dataDirectory.child("tmp/");
Core.settings.defaults("locale", "default");
Core.keybinds.setDefaults(Binding.values());
Core.settings.load();
if(!loadLocales) return;
try{
//try loading external bundle
FileHandle handle = Core.files.local("bundle");
Locale locale = Locale.ENGLISH;
Core.bundle = I18NBundle.createBundle(handle, locale);
Log.info("NOTE: external translation bundle has been loaded.");
if(!headless){
Time.run(10f, () -> ui.showInfo("Note: You have successfully loaded an external translation bundle."));
}
}catch(Throwable e){
//no external bundle found
FileHandle handle = Core.files.internal("bundles/bundle");
Locale locale;
String loc = Core.settings.getString("locale");
if(loc.equals("default")){
locale = Locale.getDefault();
}else{
Locale lastLocale;
if(loc.contains("_")){
String[] split = loc.split("_");
lastLocale = new Locale(split[0], split[1]);
}else{
lastLocale = new Locale(loc);
}
locale = lastLocale;
}
Locale.setDefault(locale);
Core.bundle = I18NBundle.createBundle(handle, locale);
}
}
}

View File

@@ -24,7 +24,7 @@ public class BlockIndexer{
private final static int quadrantSize = 16;
/** Set of all ores that are being scanned. */
private final ObjectSet<Item> scanOres = ObjectSet.with(Item.getAllOres().toArray(Item.class));
private final ObjectSet<Item> scanOres = new ObjectSet<>();
private final ObjectSet<Item> itemSet = new ObjectSet<>();
/** Stores all ore quadtrants on the map. */
private ObjectMap<Item, ObjectSet<Tile>> ores;
@@ -57,6 +57,8 @@ public class BlockIndexer{
});
Events.on(WorldLoadEvent.class, event -> {
scanOres.clear();
scanOres.addAll(Item.getAllOres());
damagedTiles = new ObjectSet[Team.all.length];
flagMap = new ObjectSet[Team.all.length][BlockFlag.all.length];

View File

@@ -15,8 +15,7 @@ import io.anuke.mindustry.world.Pos;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public class Pathfinder{
private static final long maxUpdate = Time.millisToNanos(4);
@@ -116,7 +115,7 @@ public class Pathfinder{
path.lastSearchTime = Time.millis();
//add all targets to the frontier
for(Tile other : world.indexer.getEnemy(team, BlockFlag.target)){
for(Tile other : indexer.getEnemy(team, BlockFlag.target)){
path.weights[other.x][other.y] = 0;
path.searches[other.x][other.y] = (short)path.search;
path.frontier.addFirst(other.pos());

View File

@@ -1,16 +1,14 @@
package io.anuke.mindustry.content;
import io.anuke.arc.Core;
import io.anuke.arc.function.BooleanProvider;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Tmp;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Damage;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.game.ContentList;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
@@ -24,16 +22,13 @@ import io.anuke.mindustry.world.blocks.production.*;
import io.anuke.mindustry.world.blocks.sandbox.*;
import io.anuke.mindustry.world.blocks.storage.*;
import io.anuke.mindustry.world.blocks.units.*;
import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter;
import io.anuke.mindustry.world.meta.Attribute;
import io.anuke.mindustry.world.modules.LiquidModule;
import io.anuke.mindustry.world.consumers.*;
import io.anuke.mindustry.world.meta.*;
import io.anuke.mindustry.world.modules.*;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public class Blocks implements ContentList{
public static final BooleanProvider padVisible = () -> state.rules.attackMode || state.rules.pvp || state.isEditor();
public static Block
//environment
@@ -79,7 +74,7 @@ public class Blocks implements ContentList{
duo, scatter, scorch, hail, arc, wave, lancer, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown,
//units
draugFactory, spiritFactory, phantomFactory, wraithFactory, ghoulFactory, revenantFactory, daggerFactory, crawlerFactory, titanFactory,
commandCenter, draugFactory, spiritFactory, phantomFactory, wraithFactory, ghoulFactory, revenantFactory, daggerFactory, crawlerFactory, titanFactory,
fortressFactory, repairPoint,
//upgrades
@@ -1648,17 +1643,23 @@ public class Blocks implements ContentList{
consumes.items(new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 20), new ItemStack(Items.titanium, 10));
}};
wraithFactory = new UnitFactory("wraith-factory"){{
requirements(Category.units, padVisible, ItemStack.with(Items.titanium, 30, Items.lead, 40, Items.silicon, 45));
type = UnitTypes.wraith;
produceTime = 750;
commandCenter = new CommandCenter("command-center"){{
requirements(Category.units, ItemStack.with(Items.copper, 200, Items.lead, 250, Items.silicon, 250, Items.graphite, 100));
size = 2;
consumes.power(0.6f);
health = size * size * 55;
}};
wraithFactory = new UnitFactory("wraith-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 30, Items.lead, 40, Items.silicon, 45));
type = UnitTypes.wraith;
produceTime = 700;
size = 2;
consumes.power(0.5f);
consumes.items(new ItemStack(Items.silicon, 10), new ItemStack(Items.titanium, 5));
}};
ghoulFactory = new UnitFactory("ghoul-factory"){{
requirements(Category.units, padVisible, ItemStack.with(Items.titanium, 75, Items.lead, 65, Items.silicon, 110));
requirements(Category.units, ItemStack.with(Items.titanium, 75, Items.lead, 65, Items.silicon, 110));
type = UnitTypes.ghoul;
produceTime = 1150;
size = 3;
@@ -1667,7 +1668,7 @@ public class Blocks implements ContentList{
}};
revenantFactory = new UnitFactory("revenant-factory"){{
requirements(Category.units, padVisible, ItemStack.with(Items.plastanium, 50, Items.titanium, 150, Items.lead, 150, Items.silicon, 200));
requirements(Category.units, ItemStack.with(Items.plastanium, 50, Items.titanium, 150, Items.lead, 150, Items.silicon, 200));
type = UnitTypes.revenant;
produceTime = 2000;
size = 4;
@@ -1676,7 +1677,7 @@ public class Blocks implements ContentList{
}};
daggerFactory = new UnitFactory("dagger-factory"){{
requirements(Category.units, padVisible, ItemStack.with(Items.lead, 55, Items.silicon, 35));
requirements(Category.units, ItemStack.with(Items.lead, 55, Items.silicon, 35));
type = UnitTypes.dagger;
produceTime = 850;
size = 2;
@@ -1685,17 +1686,17 @@ public class Blocks implements ContentList{
}};
crawlerFactory = new UnitFactory("crawler-factory"){{
requirements(Category.units, padVisible, ItemStack.with(Items.lead, 25, Items.silicon, 30));
requirements(Category.units, ItemStack.with(Items.lead, 45, Items.silicon, 30));
type = UnitTypes.crawler;
produceTime = 250;
produceTime = 300;
size = 2;
maxSpawn = 8;
consumes.power(0.4f);
consumes.items(new ItemStack(Items.coal, 5));
maxSpawn = 6;
consumes.power(0.5f);
consumes.items(new ItemStack(Items.coal, 10));
}};
titanFactory = new UnitFactory("titan-factory"){{
requirements(Category.units, padVisible, ItemStack.with(Items.graphite, 50, Items.lead, 50, Items.silicon, 45));
requirements(Category.units, ItemStack.with(Items.graphite, 50, Items.lead, 50, Items.silicon, 45));
type = UnitTypes.titan;
produceTime = 1050;
size = 3;
@@ -1704,7 +1705,7 @@ public class Blocks implements ContentList{
}};
fortressFactory = new UnitFactory("fortress-factory"){{
requirements(Category.units, padVisible, ItemStack.with(Items.thorium, 40, Items.lead, 110, Items.silicon, 75));
requirements(Category.units, ItemStack.with(Items.thorium, 40, Items.lead, 110, Items.silicon, 75));
type = UnitTypes.fortress;
produceTime = 2000;
size = 3;

View File

@@ -32,7 +32,7 @@ public class Fx implements ContentList{
bigShockwave, nuclearShockwave, explosion, blockExplosion, blockExplosionSmoke, shootSmall, shootHeal, shootSmallSmoke, shootBig, shootBig2, shootBigSmoke,
shootBigSmoke2, shootSmallFlame, shootPyraFlame, shootLiquid, shellEjectSmall, shellEjectMedium,
shellEjectBig, lancerLaserShoot, lancerLaserShootSmoke, lancerLaserCharge, lancerLaserChargeBegin, lightningCharge, lightningShoot,
unitSpawn, spawnShockwave, magmasmoke, impactShockwave, impactcloud, impactsmoke, dynamicExplosion, padlaunch;
unitSpawn, spawnShockwave, magmasmoke, impactShockwave, impactcloud, impactsmoke, dynamicExplosion, padlaunch, commandSend;
@Override
public void load(){
@@ -53,6 +53,13 @@ public class Fx implements ContentList{
Draw.reset();
});
commandSend = new Effect(28, e -> {
Draw.color(Pal.command);
Lines.stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 4f + e.finpow() * 120f);
Draw.color();
});
placeBlock = new Effect(16, e -> {
Draw.color(Pal.accent);
Lines.stroke(3f - e.fin() * 2f);

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
@@ -85,7 +86,7 @@ public class Mechs implements ContentList{
Effects.shake(1f, 1f, player);
Effects.effect(Fx.landShock, player);
for(int i = 0; i < 8; i++){
Time.run(Mathf.random(8f), () -> Lightning.create(player.getTeam(), Pal.lancerLaser, 17f, player.x, player.y, Mathf.random(360f), 14));
Time.run(Mathf.random(8f), () -> Lightning.create(player.getTeam(), Pal.lancerLaser, 17f * Vars.state.rules.playerDamageMultiplier, player.x, player.y, Mathf.random(360f), 14));
}
}
}
@@ -286,7 +287,7 @@ public class Mechs implements ContentList{
float scl = scld(player);
if(Mathf.chance(Time.delta() * (0.15 * scl))){
Effects.effect(Fx.hitLancer, Pal.lancerLaser, player.x, player.y);
Lightning.create(player.getTeam(), Pal.lancerLaser, 10f,
Lightning.create(player.getTeam(), Pal.lancerLaser, 10f * Vars.state.rules.playerDamageMultiplier,
player.x + player.velocity().x, player.y + player.velocity().y, player.rotation, 14);
}
}

View File

@@ -254,6 +254,7 @@ public class TechTree implements ContentList{
});
node(daggerFactory, () -> {
node(commandCenter, () -> {});
node(crawlerFactory, () -> {
node(titanFactory, () -> {
node(fortressFactory, () -> {
@@ -296,7 +297,7 @@ public class TechTree implements ContentList{
private TechNode node(Block block, Runnable children){
ItemStack[] requirements = new ItemStack[block.buildRequirements.length];
for(int i = 0; i < requirements.length; i++){
requirements[i] = new ItemStack(block.buildRequirements[i].item, 30 + block.buildRequirements[i].amount * 5);
requirements[i] = new ItemStack(block.buildRequirements[i].item, 30 + block.buildRequirements[i].amount * 6);
}
return new TechNode(block, requirements, children);

View File

@@ -1,16 +1,14 @@
package io.anuke.mindustry.core;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.Pixmap;
import io.anuke.arc.util.Log;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.LegacyColorMapper;
import io.anuke.mindustry.world.*;
import static io.anuke.arc.Core.files;
@@ -21,8 +19,6 @@ import static io.anuke.arc.Core.files;
@SuppressWarnings("unchecked")
public class ContentLoader{
private boolean loaded = false;
private boolean verbose = false;
private ObjectMap<String, MappableContent>[] contentNameMap = new ObjectMap[ContentType.values().length];
private Array<Content>[] contentMap = new Array[ContentType.values().length];
private MappableContent[][] temporaryMapper;
@@ -45,12 +41,8 @@ public class ContentLoader{
new LegacyColorMapper(),
};
public void setVerbose(){
verbose = true;
}
/** Creates all content types. */
public void load(){
public void createContent(){
if(loaded){
Log.info("Content already loaded, skipping.");
return;
@@ -65,8 +57,6 @@ public class ContentLoader{
list.load();
}
int total = 0;
for(ContentType type : ContentType.values()){
for(Content c : contentMap[type.ordinal()]){
@@ -77,7 +67,6 @@ public class ContentLoader{
}
contentNameMap[type.ordinal()].put(name, (MappableContent)c);
}
total++;
}
}
@@ -91,25 +80,32 @@ public class ContentLoader{
}
}
if(verbose){
Log.info("--- CONTENT INFO ---");
for(int k = 0; k < contentMap.length; k++){
Log.info("[{0}]: loaded {1}", ContentType.values()[k].name(), contentMap[k].size);
}
Log.info("Total content loaded: {0}", total);
Log.info("-------------------");
}
loaded = true;
}
public void initialize(Consumer<Content> callable){
initialize(callable, false);
/** Logs content statistics.*/
public void logContent(){
Log.info("--- CONTENT INFO ---");
for(int k = 0; k < contentMap.length; k++){
Log.info("[{0}]: loaded {1}", ContentType.values()[k].name(), contentMap[k].size);
}
Log.info("Total content loaded: {0}", Array.with(ContentType.values()).mapInt(c -> contentMap[c.ordinal()].size).sum());
Log.info("-------------------");
}
/** Calls Content#init() on everything. Use only after all modules have been created.*/
public void init(){
initialize(Content::init);
}
/** Calls Content#load() on everything. Use only after all modules have been created on the client.*/
public void load(){
initialize(Content::load);
}
/** Initializes all content with the specified function. */
public void initialize(Consumer<Content> callable, boolean override){
if(initialization.contains(callable) && !override) return;
private void initialize(Consumer<Content> callable){
if(initialization.contains(callable)) return;
for(ContentType type : ContentType.values()){
for(Content content : contentMap[type.ordinal()]){
@@ -136,12 +132,8 @@ public class ContentLoader{
pixmap.dispose();
}
public void verbose(boolean verbose){
this.verbose = verbose;
}
public void dispose(){
//clear all content, currently not needed
//clear all content, currently not used
}
public void handleContent(Content content){
@@ -171,7 +163,7 @@ public class ContentLoader{
return null;
}
if(temporaryMapper[type.ordinal()].length <= id || temporaryMapper[type.ordinal()][id] == null){
return getByID(type, 0); //default value is always ID 0
return (T)contentMap[type.ordinal()].get(0); //default value is always ID 0
}
return (T)temporaryMapper[type.ordinal()][id];
}

View File

@@ -1,6 +1,7 @@
package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
@@ -13,8 +14,8 @@ import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.maps.*;
@@ -25,6 +26,8 @@ import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.storage.*;
import java.io.*;
import java.time.*;
import java.time.format.*;
import static io.anuke.arc.Core.*;
import static io.anuke.mindustry.Vars.*;
@@ -35,10 +38,10 @@ import static io.anuke.mindustry.Vars.*;
* Should <i>not</i> handle any logic-critical state.
* This class is not created in the headless server.
*/
public class Control implements ApplicationListener{
public final Saves saves;
public final MusicControl music;
public final Tutorial tutorial;
public class Control implements ApplicationListener, Loadable{
public Saves saves;
public MusicControl music;
public Tutorial tutorial;
public InputHandler input;
private Interval timer = new Interval(2);
@@ -46,37 +49,6 @@ public class Control implements ApplicationListener{
private boolean wasPaused = false;
public Control(){
batch = new SpriteBatch();
saves = new Saves();
tutorial = new Tutorial();
music = new MusicControl();
UnitScl.dp.setProduct(settings.getInt("uiscale", 100) / 100f);
Core.input.setCatch(KeyCode.BACK, true);
content.initialize(Content::init);
Core.atlas = new TextureAtlas("sprites/sprites.atlas");
Draw.scl = 1f / Core.atlas.find("scale_marker").getWidth();
content.initialize(Content::load, true);
data.load();
Core.settings.setAppName(appName);
Core.settings.defaults(
"ip", "localhost",
"color-0", Color.rgba8888(playerColors[8]),
"color-1", Color.rgba8888(playerColors[11]),
"color-2", Color.rgba8888(playerColors[13]),
"color-3", Color.rgba8888(playerColors[9]),
"name", "",
"lastBuild", 0
);
createPlayer();
saves.load();
Events.on(StateChangeEvent.class, event -> {
if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){
Time.runTask(5f, Platform.instance::updateRPC);
@@ -177,12 +149,32 @@ public class Control implements ApplicationListener{
Events.on(ZoneConfigureCompleteEvent.class, e -> {
ui.hudfrag.showToast(Core.bundle.format("zone.config.complete", e.zone.configureWave));
});
}
if(android){
Sounds.empty.loop(0f, 1f, 0f);
@Override
public void loadAsync(){
saves = new Saves();
tutorial = new Tutorial();
music = new MusicControl();
checkClassicData();
}
Draw.scl = 1f / Core.atlas.find("scale_marker").getWidth();
UnitScl.dp.setProduct(settings.getInt("uiscale", 100) / 100f);
Core.input.setCatch(KeyCode.BACK, true);
data.load();
Core.settings.defaults(
"ip", "localhost",
"color-0", Color.rgba8888(playerColors[8]),
"name", "",
"lastBuild", 0
);
createPlayer();
saves.load();
}
//checks for existing 3.5 app data, android only
@@ -240,6 +232,9 @@ public class Control implements ApplicationListener{
world.loadMap(map, rules);
state.rules = rules;
logic.play();
if(settings.getBool("savecreate")){
control.saves.addSave(map.name() + "-" + DateTimeFormatter.ofPattern("MMM dd h:mm").format(LocalDateTime.now()));
}
});
}
@@ -382,6 +377,11 @@ public class Control implements ApplicationListener{
dialog.show();
}));
}
if(android){
Sounds.empty.loop(0f, 1f, 0f);
checkClassicData();
}
}
@Override
@@ -396,6 +396,17 @@ public class Control implements ApplicationListener{
music.update();
loops.update();
if(Core.input.keyTap(Binding.fullscreen)){
boolean full = settings.getBool("fullscreen");
if(full){
graphics.setWindowedMode(graphics.getWidth(), graphics.getHeight());
}else{
graphics.setFullscreenMode(graphics.getDisplayMode());
}
settings.put("fullscreen", !full);
settings.save();
}
if(!state.is(State.menu)){
input.update();

View File

@@ -101,15 +101,15 @@ public class Logic implements ApplicationListener{
state.rules = new Rules();
state.stats = new Stats();
entities.clear();
Time.clear();
Entities.clear();
TileEntity.sleepingEntities = 0;
Events.fire(new ResetEvent());
}
public void runWave(){
world.spawner.spawnEnemies();
spawner.spawnEnemies();
state.wave++;
state.wavetime = world.isZone() && world.getZone().isBossWave(state.wave) ? state.rules.waveSpacing * state.rules.bossWaveMultiplier :
world.isZone() && world.getZone().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
@@ -195,20 +195,20 @@ public class Logic implements ApplicationListener{
}
if(!headless){
Entities.update(effectGroup);
Entities.update(groundEffectGroup);
effectGroup.update();
groundEffectGroup.update();
}
if(!state.isEditor()){
for(EntityGroup group : unitGroups){
Entities.update(group);
group.update();
}
Entities.update(puddleGroup);
Entities.update(shieldGroup);
Entities.update(bulletGroup);
Entities.update(tileGroup);
Entities.update(fireGroup);
puddleGroup.update();
shieldGroup.update();
bulletGroup.update();
tileGroup.update();
fireGroup.update();
}else{
for(EntityGroup<?> group : unitGroups){
group.updateEvents();
@@ -217,11 +217,11 @@ public class Logic implements ApplicationListener{
}
Entities.update(playerGroup);
playerGroup.update();
//effect group only contains item transfers in the headless version, update it!
if(headless){
Entities.update(effectGroup);
effectGroup.update();
}
if(!state.isEditor()){
@@ -234,7 +234,7 @@ public class Logic implements ApplicationListener{
collisions.collideGroups(bulletGroup, playerGroup);
}
world.pathfinder.update();
pathfinder.update();
}
if(!Net.client() && !world.isInvalidMap() && !state.isEditor()){

View File

@@ -7,11 +7,11 @@ import io.anuke.arc.collection.IntSet;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.RandomXS128;
import io.anuke.arc.util.*;
import io.anuke.arc.util.CommandHandler.*;
import io.anuke.arc.util.io.ReusableByteInStream;
import io.anuke.arc.util.serialization.Base64Coder;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.SyncTrait;
@@ -151,15 +151,38 @@ public class NetClient implements ApplicationListener{
throw new ValidateException(player, "Player has sent a message above the text limit.");
}
//server console logging
Log.info("&y{0}: &lb{1}", player.name, message);
//check if it's a command
CommandResponse response = netServer.clientCommands.handleMessage(message, player);
if(response.type == ResponseType.noCommand){ //no command to handle
//server console logging
Log.info("&y{0}: &lb{1}", player.name, message);
//invoke event for all clients but also locally
//this is required so other clients get the correct name even if they don't know who's sending it yet
Call.sendMessage(message, colorizeName(player.id, player.name), player);
//invoke event for all clients but also locally
//this is required so other clients get the correct name even if they don't know who's sending it yet
Call.sendMessage(message, colorizeName(player.id, player.name), player);
}else{
//log command to console but with brackets
Log.info("<&y{0}: &lm{1}&lg>", player.name, message);
//a command was sent, now get the output
if(response.type != ResponseType.valid){
String text;
//send usage
if(response.type == ResponseType.manyArguments){
text = "[scarlet]Too many arguments. Usage:[lightgray] " + response.command.text + "[gray] " + response.command.paramText;
}else if(response.type == ResponseType.fewArguments){
text = "[scarlet]Too few arguments. Usage:[lightgray] " + response.command.text + "[gray] " + response.command.paramText;
}else{ //unknown command
text = "[scarlet]Unknown command. Check [lightgray]/help[scarlet].";
}
player.sendMessage(text);
}
}
}
private static String colorizeName(int id, String name){
public static String colorizeName(int id, String name){
Player player = playerGroup.getByID(id);
if(name == null || player == null) return null;
return "[#" + player.color.toString().toUpperCase() + "]" + name;
@@ -195,7 +218,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.both)
public static void onWorldDataBegin(){
Entities.clear();
entities.clear();
netClient.removed.clear();
logic.reset();
@@ -229,7 +252,7 @@ public class NetClient implements ApplicationListener{
netClient.byteStream.setBytes(Net.decompressSnapshot(data, dataLen));
DataInputStream input = netClient.dataStream;
EntityGroup group = Entities.getGroup(groupID);
EntityGroup group = entities.get(groupID);
//go through each entity
for(int j = 0; j < amount; j++){
@@ -347,7 +370,7 @@ public class NetClient implements ApplicationListener{
quiet = false;
lastSent = 0;
Entities.clear();
entities.clear();
ui.chatfrag.clearMessages();
}

View File

@@ -4,18 +4,17 @@ import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.ApplicationListener;
import io.anuke.arc.Events;
import io.anuke.arc.collection.IntMap;
import io.anuke.arc.collection.ObjectSet;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.Colors;
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.*;
import io.anuke.arc.util.CommandHandler.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.Entity;
@@ -47,6 +46,7 @@ public class NetServer implements ApplicationListener{
private final static float correctDist = 16f;
public final Administration admins = new Administration();
public final CommandHandler clientCommands = new CommandHandler("/");
/** Maps connection IDs to players. */
private IntMap<Player> connections = new IntMap<>();
@@ -112,7 +112,7 @@ public class NetServer implements ApplicationListener{
}
if(packet.versionType == null || ((packet.version == -1 || !packet.versionType.equals(Version.type)) && Version.build != -1 && !admins.allowsCustomClients())){
kick(id, KickReason.customClient);
kick(id, !Version.type.equals(packet.versionType) ? KickReason.typeMismatch : KickReason.customClient);
return;
}
@@ -191,6 +191,138 @@ public class NetServer implements ApplicationListener{
if(player == null) return;
RemoteReadServer.readPacket(packet.writeBuffer, packet.type, player);
});
registerCommands();
}
private void registerCommands(){
clientCommands.<Player>register("help", "[page]", "Lists all commands.", (args, player) -> {
if(args.length > 0 && !Strings.canParseInt(args[0])){
player.sendMessage("[scarlet]'page' must be a number.");
return;
}
int commandsPerPage = 6;
int page = args.length > 0 ? Strings.parseInt(args[0]) : 1;
int pages = Mathf.ceil((float)clientCommands.getCommandList().size / commandsPerPage);
page --;
if(page > pages || page < 0){
player.sendMessage("[scarlet]'page' must be a number between[orange] 1[] and[orange] " + pages + "[scarlet].");
return;
}
StringBuilder result = new StringBuilder();
result.append(Strings.format("[orange]-- Commands Page[lightgray] {0}[gray]/[lightgray]{1}[orange] --\n\n", (page+1), pages));
for(int i = commandsPerPage * page; i < Math.min(commandsPerPage * (page + 1), clientCommands.getCommandList().size); i++){
Command command = clientCommands.getCommandList().get(i);
result.append("[orange] /").append(command.text).append("[white] ").append(command.paramText).append("[lightgray] - ").append(command.description).append("\n");
}
player.sendMessage(result.toString());
});
clientCommands.<Player>register("t", "<message...>", "Send a message only to your teammates.", (args, player) -> {
playerGroup.all().each(p -> p.getTeam() == player.getTeam(), o -> o.sendMessage(args[0], player, "[#" + player.getTeam().color.toString() + "]<T>" + NetClient.colorizeName(player.id, player.name)));
});
//duration of a a kick in seconds
int kickDuration = 10 * 60;
class VoteSession{
Player target;
ObjectSet<String> voted = new ObjectSet<>();
ObjectMap<Player, VoteSession> map;
Timer.Task task;
int votes;
public VoteSession(ObjectMap<Player, VoteSession> map, Player target){
this.target = target;
this.map = map;
this.task = Timer.schedule(() -> {
if(!checkPass()){
Call.sendMessage(Strings.format("[lightgray]Vote failed. Not enough votes to kick[orange] {0}[lightgray].", target.name));
}
map.remove(target);
task.cancel();
}, 60 * 1.5f);
}
boolean checkPass(){
if(votes >= votesRequired() && target.isAdded() && target.con.isConnected()){
Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] {0}[orange] will be kicked from the server.", target.name));
admins.getInfo(target.uuid).lastKicked = Time.millis() + kickDuration*1000;
kick(target.con.id, KickReason.vote);
return true;
}
return false;
}
}
//cooldown between votes
int voteTime = 60 * 10;
Timekeeper vtime = new Timekeeper(voteTime);
//current kick sessions
ObjectMap<Player, VoteSession> currentlyKicking = new ObjectMap<>();
clientCommands.<Player>register("votekick", "[player...]", "Vote to kick a player, with a cooldown.", (args, player) -> {
if(playerGroup.size() < 3){
player.sendMessage("[scarlet]At least 3 players are needed to start a votekick.");
return;
}
if(currentlyKicking.values().toArray().contains(v -> v.voted.contains(player.uuid) || v.voted.contains(admins.getInfo(player.uuid).lastIP))){
player.sendMessage("[scarlet]You've already voted. Sit down.");
return;
}
if(args.length == 0){
StringBuilder builder = new StringBuilder();
builder.append("[orange]Players to kick: \n");
for(Player p : playerGroup.all()){
if(p.isAdmin || p.con == null || p == player) continue;
builder.append("[lightgray] ").append(p.name).append("[accent] (#").append(p.con.id).append(")\n");
}
player.sendMessage(builder.toString());
}else{
Player found;
if(args[0].length() > 1 && args[0].startsWith("#") && Strings.canParseInt(args[0].substring(1))){
int id = Strings.parseInt(args[0].substring(1));
found = playerGroup.find(p -> p.con != null && p.con.id == id);
}else{
found = playerGroup.find(p -> p.name.equalsIgnoreCase(args[0]));
}
if(found != null){
if(player == found){
player.sendMessage("[scarlet]If you're interested in kicking yourself, just leave.");
}else if(found.isAdmin){
player.sendMessage("[scarlet]Did you really expect to be able to kick an admin?");
}else{
if(!currentlyKicking.containsKey(found) && !vtime.get()){
player.sendMessage("[scarlet]You must wait " + voteTime/60 + " minutes between votekicks.");
return;
}
VoteSession session = currentlyKicking.getOr(found, () -> new VoteSession(currentlyKicking, found));
session.votes ++;
session.voted.addAll(player.uuid, admins.getInfo(player.uuid).lastIP);
Call.sendMessage(Strings.format("[orange]{0}[lightgray] has voted to kick[orange] {1}[].[accent] ({2}/{3})\n[lightgray]Type[orange] /votekick #{4}[] to agree.",
player.name, found.name, session.votes, votesRequired(), found.con.id));
session.checkPass();
vtime.reset();
}
}else{
player.sendMessage("[scarlet]No player[orange]'" + args[0] + "'[scarlet] found.");
}
}
});
}
public int votesRequired(){
return playerGroup.size() * 2 / 3;
}
public Team assignTeam(Player current, Iterable<Player> players){
@@ -422,10 +554,10 @@ public class NetServer implements ApplicationListener{
Player player = connections.get(con.id);
if(player != null && (reason == KickReason.kick || reason == KickReason.banned) && player.uuid != null){
if(player != null && (reason == KickReason.kick || reason == KickReason.banned || reason == KickReason.vote) && player.uuid != null){
PlayerInfo info = admins.getInfo(player.uuid);
info.timesKicked++;
info.lastKicked = Time.millis();
info.lastKicked = Math.max(Time.millis(), info.lastKicked);
}
Call.onKick(connection, reason);
@@ -455,7 +587,7 @@ public class NetServer implements ApplicationListener{
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);
//check for syncable groups
for(EntityGroup<?> group : Entities.getAllGroups()){
for(EntityGroup<?> group : entities.all()){
if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue;
//make sure mapping is enabled for this group

View File

@@ -43,9 +43,6 @@ public class Renderer implements ApplicationListener{
public Renderer(){
camera = new Camera();
if(settings.getBool("bloom")){
setupBloom();
}
Shaders.init();
Effects.setScreenShakeProvider((intensity, duration) -> {
@@ -93,6 +90,13 @@ public class Renderer implements ApplicationListener{
clearColor = new Color(0f, 0f, 0f, 1f);
}
@Override
public void init(){
if(settings.getBool("bloom")){
setupBloom();
}
}
@Override
public void update(){
//TODO hack, find source of this bug
@@ -205,9 +209,9 @@ public class Renderer implements ApplicationListener{
blocks.floor.drawFloor();
draw(groundEffectGroup, e -> e instanceof BelowLiquidTrait);
draw(puddleGroup);
draw(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait));
groundEffectGroup.draw(e -> e instanceof BelowLiquidTrait);
puddleGroup.draw();
groundEffectGroup.draw(e -> !(e instanceof BelowLiquidTrait));
blocks.processBlocks();
@@ -244,8 +248,8 @@ public class Renderer implements ApplicationListener{
bloom.capture();
}
draw(bulletGroup);
draw(effectGroup);
bulletGroup.draw();
effectGroup.draw();
Draw.flush();
if(bloom != null && !pixelator.enabled()){
@@ -253,15 +257,15 @@ public class Renderer implements ApplicationListener{
}
overlays.drawBottom();
draw(playerGroup, p -> true, Player::drawBuildRequests);
playerGroup.draw(p -> true, Player::drawBuildRequests);
if(Entities.countInBounds(shieldGroup) > 0){
if(settings.getBool("animatedshields")){
if(shieldGroup.countInBounds() > 0){
if(settings.getBool("animatedshields") && Shaders.shield != null){
Draw.flush();
shieldBuffer.begin();
graphics.clear(Color.CLEAR);
Entities.draw(shieldGroup);
Entities.draw(shieldGroup, shield -> true, shield -> ((ShieldEntity)shield).drawOver());
shieldGroup.draw();
shieldGroup.draw(shield -> true, ShieldEntity::drawOver);
Draw.flush();
shieldBuffer.end();
Draw.shader(Shaders.shield);
@@ -270,13 +274,13 @@ public class Renderer implements ApplicationListener{
Draw.color();
Draw.shader();
}else{
Entities.draw(shieldGroup, shield -> true, shield -> ((ShieldEntity)shield).drawSimple());
shieldGroup.draw(shield -> true, ShieldEntity::drawSimple);
}
}
overlays.drawTop();
draw(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName);
playerGroup.draw(p -> !p.isDead() && !p.isLocal, Player::drawName);
Draw.color();
Draw.flush();
@@ -293,12 +297,12 @@ public class Renderer implements ApplicationListener{
for(EntityGroup<? extends BaseUnit> group : unitGroups){
if(!group.isEmpty()){
draw(group, unit -> !unit.isDead(), draw::accept);
group.draw(unit -> !unit.isDead(), draw::accept);
}
}
if(!playerGroup.isEmpty()){
draw(playerGroup, unit -> !unit.isDead(), draw::accept);
playerGroup.draw(unit -> !unit.isDead(), draw::accept);
}
Draw.color();
@@ -310,12 +314,12 @@ public class Renderer implements ApplicationListener{
for(EntityGroup<? extends BaseUnit> group : unitGroups){
if(!group.isEmpty()){
draw(group, unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY));
group.draw(unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY));
}
}
if(!playerGroup.isEmpty()){
draw(playerGroup, unit -> unit.isFlying() && !unit.isDead(), player -> player.drawShadow(trnsX, trnsY));
playerGroup.draw(unit -> unit.isFlying() && !unit.isDead(), player -> player.drawShadow(trnsX, trnsY));
}
Draw.color();
@@ -327,29 +331,17 @@ public class Renderer implements ApplicationListener{
if(group.count(p -> p.isFlying() == flying) + playerGroup.count(p -> p.isFlying() == flying && p.getTeam() == team) == 0 && flying) continue;
draw(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder);
draw(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team && !p.isDead(), Unit::drawUnder);
unitGroups[team.ordinal()].draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder);
playerGroup.draw(p -> p.isFlying() == flying && p.getTeam() == team && !p.isDead(), Unit::drawUnder);
draw(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll);
draw(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawAll);
unitGroups[team.ordinal()].draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll);
playerGroup.draw(p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawAll);
draw(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver);
draw(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver);
unitGroups[team.ordinal()].draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver);
playerGroup.draw(p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver);
}
}
public <T extends DrawTrait> void draw(EntityGroup<T> group){
draw(group, t -> true, DrawTrait::draw);
}
public <T extends DrawTrait> void draw(EntityGroup<T> group, Predicate<T> toDraw){
draw(group, toDraw, DrawTrait::draw);
}
public <T extends DrawTrait> void draw(EntityGroup<T> group, Predicate<T> toDraw, Consumer<T> drawer){
Entities.draw(group, toDraw, drawer);
}
public void scaleCamera(float amount){
targetscale += amount;
clampScale();

View File

@@ -3,8 +3,13 @@ package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.Graphics.*;
import io.anuke.arc.Graphics.Cursor.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.assets.loaders.*;
import io.anuke.arc.assets.loaders.resolvers.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.freetype.*;
import io.anuke.arc.freetype.FreeTypeFontGenerator.*;
import io.anuke.arc.freetype.FreetypeFontLoader.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
@@ -31,8 +36,8 @@ import io.anuke.mindustry.ui.fragments.*;
import static io.anuke.arc.scene.actions.Actions.*;
import static io.anuke.mindustry.Vars.*;
public class UI implements ApplicationListener{
private FreeTypeFontGenerator generator;
public class UI implements ApplicationListener, Loadable{
private Skin skin;
public MenuFragment menufrag;
public HudFragment hudfrag;
@@ -67,9 +72,23 @@ public class UI implements ApplicationListener{
public Cursor drillCursor, unloadCursor;
public UI(){
Skin skin = new Skin(Core.atlas);
generateFonts(skin);
skin = new Skin();
setupFonts();
}
@Override
public void loadAsync(){
}
@Override
public void loadSync(){
//TODO type-safe skin files
skin.addRegions(Core.atlas);
loadExtraStyle(skin);
skin.add("outline", Core.assets.get("outline"));
skin.getFont("default").getData().markupEnabled = true;
skin.getFont("default").setOwnsTexture(false);
skin.load(Core.files.internal("sprites/uiskin.json"));
for(BitmapFont font : skin.getAll(BitmapFont.class).values()){
@@ -97,6 +116,11 @@ public class UI implements ApplicationListener{
loadExtraCursors();
}
@Override
public Array<AssetDescriptor> getDependencies(){
return Array.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", BitmapFont.class), new AssetDescriptor<>("default", BitmapFont.class), new AssetDescriptor<>("chat", BitmapFont.class));
}
/** Called from a static context to make the cursor appear immediately upon startup.*/
public static void loadSystemCursors(){
SystemCursor.arrow.set(Core.graphics.newCursor("cursor"));
@@ -106,6 +130,23 @@ public class UI implements ApplicationListener{
Core.graphics.restoreCursor();
}
/** Called from a static context for use in the loading screen.*/
public static void loadDefaultFont(){
FileHandleResolver resolver = new InternalFileHandleResolver();
Core.assets.setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(resolver));
Core.assets.setLoader(BitmapFont.class, null, new FreetypeFontLoader(resolver));
FreeTypeFontParameter param = new FreeTypeFontParameter(){{
size = fontParameter().size;
borderColor = Color.DARK_GRAY;
borderWidth = UnitScl.dp.scl(2f);
spaceX -= borderWidth;
incremental = true;
}};
Core.assets.load("outline", BitmapFont.class, new FreeTypeFontLoaderParameter("fonts/font.ttf", param));
}
void loadExtraStyle(Skin skin){
AtlasRegion region = Core.atlas.find("flat-down-base");
int[] splits = region.splits;
@@ -130,29 +171,22 @@ public class UI implements ApplicationListener{
unloadCursor = Core.graphics.newCursor("unload");
}
void generateFonts(Skin skin){
generator = new FreeTypeFontGenerator(Core.files.internal("fonts/font.ttf"));
public void setupFonts(){
String fontName = "fonts/font.ttf";
FreeTypeFontParameter param = new FreeTypeFontParameter(){{
FreeTypeFontParameter param = fontParameter();
Core.assets.load("default", BitmapFont.class, new FreeTypeFontLoaderParameter(fontName, param)).loaded = f -> skin.add("default", f);
Core.assets.load("chat", BitmapFont.class, new FreeTypeFontLoaderParameter(fontName, param)).loaded = f -> skin.add("chat", f);
}
static FreeTypeFontParameter fontParameter(){
return new FreeTypeFontParameter(){{
size = (int)(UnitScl.dp.scl(18f));
shadowColor = Color.DARK_GRAY;
shadowOffsetY = 2;
incremental = true;
}};
FreeTypeFontParameter outlined = new FreeTypeFontParameter(){{
size = param.size;
borderColor = Color.DARK_GRAY;
borderWidth = UnitScl.dp.scl(2f);
spaceX -= borderWidth;
incremental = true;
}};
skin.add("outline", generator.generateFont(outlined));
skin.add("default", generator.generateFont(param));
skin.add("chat", generator.generateFont(param));
skin.getFont("default").getData().markupEnabled = true;
skin.getFont("default").setOwnsTexture(false);
}
@Override
@@ -225,13 +259,14 @@ public class UI implements ApplicationListener{
@Override
public void resize(int width, int height){
if(Core.scene == null) return;
Core.scene.resize(width, height);
Events.fire(new ResizeEvent());
}
@Override
public void dispose(){
generator.dispose();
//generator.dispose();
}
public void loadAnd(Runnable call){

View File

@@ -6,10 +6,8 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.ai.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
@@ -23,11 +21,7 @@ import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.*;
public class World implements ApplicationListener{
public final Maps maps = new Maps();
public final BlockIndexer indexer = new BlockIndexer();
public final WaveSpawner spawner = new WaveSpawner();
public final Pathfinder pathfinder = new Pathfinder();
public class World{
public final Context context = new Context();
private Map currentMap;
@@ -36,17 +30,7 @@ public class World implements ApplicationListener{
private boolean generating, invalidMap;
public World(){
maps.load();
}
@Override
public void init(){
maps.loadLegacyMaps();
}
@Override
public void dispose(){
maps.dispose();
}
public boolean isInvalidMap(){
@@ -190,9 +174,11 @@ public class World implements ApplicationListener{
}
}
addDarkness(tiles);
if(!headless){
addDarkness(tiles);
}
Entities.getAllGroups().each(group -> group.resize(-finalWorldBounds, -finalWorldBounds, tiles.length * tilesize + finalWorldBounds * 2, tiles[0].length * tilesize + finalWorldBounds * 2));
entities.all().each(group -> group.resize(-finalWorldBounds, -finalWorldBounds, tiles.length * tilesize + finalWorldBounds * 2, tiles[0].length * tilesize + finalWorldBounds * 2));
generating = false;
Events.fire(new WorldLoadEvent());
@@ -354,7 +340,7 @@ public class World implements ApplicationListener{
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(tile.block().solid && !tile.block().synthetic() && tile.block().fillsTile){
if(tile.isDarkened()){
dark[x][y] = darkIterations;
}
}
@@ -383,9 +369,21 @@ public class World implements ApplicationListener{
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(tile.block().solid && !tile.block().synthetic()){
if(tile.isDarkened()){
tiles[x][y].rotation(dark[x][y]);
}
if(dark[x][y] == 4){
boolean full = true;
for(Point2 p : Geometry.d4){
int px = p.x + x, py = p.y + y;
if(Structs.inBounds(px, py, tiles) && !(tiles[px][py].isDarkened() && dark[px][py] == 4)){
full = false;
break;
}
}
if(full) tiles[x][y].rotation(5);
}
}
}
}

View File

@@ -93,11 +93,11 @@ public class MapEditorDialog extends Dialog implements Disposable{
"$editor.importmap", "$editor.importmap.description", "icon-load-map", (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", "icon-file", (Runnable)() ->
Platform.instance.showFileChooser("$editor.loadmap", "Map Files", file -> ui.loadAnd(() -> {
world.maps.tryCatchMapError(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
}else if(file.extension().equalsIgnoreCase(oldMapExtension)){
editor.beginEdit(world.maps.makeLegacyMap(file));
editor.beginEdit(maps.makeLegacyMap(file));
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
@@ -286,11 +286,11 @@ public class MapEditorDialog extends Dialog implements Disposable{
infoDialog.show();
Core.app.post(() -> ui.showError("$editor.save.noname"));
}else{
Map map = world.maps.all().find(m -> m.name().equals(name));
Map map = maps.all().find(m -> m.name().equals(name));
if(map != null && !map.custom){
handleSaveBuiltin(map);
}else{
world.maps.saveMap(editor.getTags());
maps.saveMap(editor.getTags());
ui.showInfoFade("$editor.saved");
}
}

View File

@@ -72,7 +72,7 @@ public class MapGenerateDialog extends FloatingDialog{
}).size(160f, 64f);
}else{
buttons.addButton("$settings.reset", () -> {
filters.set(world.maps.readFilters(""));
filters.set(maps.readFilters(""));
rebuildFilters();
update();
}).size(160f, 64f);
@@ -304,7 +304,7 @@ public class MapGenerateDialog extends FloatingDialog{
}
selection.cont.addButton("$filter.defaultores", () -> {
world.maps.addDefaultOres(filters);
maps.addDefaultOres(filters);
rebuildFilters();
update();
selection.hide();

View File

@@ -9,8 +9,6 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.world;
public class MapInfoDialog extends FloatingDialog{
private final MapEditor editor;
private final WaveInfoDialog waveInfo;
@@ -70,7 +68,7 @@ public class MapInfoDialog extends FloatingDialog{
t.row();
t.add("$editor.generation").padRight(8).left();
t.addButton("$edit",
() -> generate.show(world.maps.readFilters(editor.getTags().get("genfilters", "")),
() -> generate.show(Vars.maps.readFilters(editor.getTags().get("genfilters", "")),
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)))
).left().width(200f);

View File

@@ -1,14 +1,14 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Scaling;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.ui.BorderImage;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.maps;
public class MapLoadDialog extends FloatingDialog{
private Map selected = null;
@@ -17,7 +17,6 @@ public class MapLoadDialog extends FloatingDialog{
super("$editor.loadmap");
shown(this::rebuild);
rebuild();
TextButton button = new TextButton("$load");
button.setDisabled(() -> selected == null);
@@ -35,8 +34,8 @@ public class MapLoadDialog extends FloatingDialog{
public void rebuild(){
cont.clear();
if(world.maps.all().size > 0){
selected = world.maps.all().first();
if(maps.all().size > 0){
selected = maps.all().first();
}
ButtonGroup<TextButton> group = new ButtonGroup<>();
@@ -52,7 +51,7 @@ public class MapLoadDialog extends FloatingDialog{
ScrollPane pane = new ScrollPane(table, "horizontal");
pane.setFadeScrollBars(false);
for(Map map : world.maps.all()){
for(Map map : maps.all()){
TextButton button = new TextButton(map.name(), "toggle");
button.add(new BorderImage(map.texture, 2f).setScaling(Scaling.fit)).size(16 * 4f);
@@ -64,7 +63,7 @@ public class MapLoadDialog extends FloatingDialog{
if(++i % maxcol == 0) table.row();
}
if(world.maps.all().size == 0){
if(maps.all().size == 0){
table.add("$maps.none").center();
}else{
cont.add("$editor.loadmap");

View File

@@ -8,7 +8,7 @@ import io.anuke.arc.graphics.Texture;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Disposable;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.graphics.IndexedRenderer;

View File

@@ -1,14 +1,13 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.scene.ui.TextButton;
import io.anuke.arc.scene.ui.TextField;
import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.arc.function.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.ui;
import static io.anuke.mindustry.Vars.world;
public class MapSaveDialog extends FloatingDialog{
private TextField field;
@@ -24,7 +23,7 @@ public class MapSaveDialog extends FloatingDialog{
shown(() -> {
cont.clear();
cont.label(() -> {
Map map = world.maps.byName(field.getText());
Map map = Vars.maps.byName(field.getText());
if(map != null){
if(map.custom){
return "$editor.overwrite";
@@ -69,7 +68,7 @@ public class MapSaveDialog extends FloatingDialog{
if(field.getText().isEmpty()){
return true;
}
Map map = world.maps.byName(field.getText());
Map map = Vars.maps.byName(field.getText());
return map != null && !map.custom;
}
}

View File

@@ -52,13 +52,13 @@ public class WaveInfoDialog extends FloatingDialog{
dialog.cont.defaults().size(210f, 64f);
dialog.cont.addButton("$waves.copy", () -> {
ui.showInfoFade("$waves.copied");
Core.app.setClipboardText(world.maps.writeWaves(groups));
Core.app.setClipboardText(maps.writeWaves(groups));
dialog.hide();
}).disabled(b -> groups == null);
dialog.cont.row();
dialog.cont.addButton("$waves.load", () -> {
try{
groups = world.maps.readWaves(Core.app.getClipboardText());
groups = maps.readWaves(Core.app.getClipboardText());
buildGroups();
}catch(Exception e){
ui.showError("$waves.invalid");

View File

@@ -1,89 +1,33 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntMap;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.graphics.Camera;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.mindustry.entities.traits.Entity;
import static io.anuke.mindustry.Vars.collisions;
import io.anuke.arc.collection.*;
import io.anuke.mindustry.entities.traits.*;
/** Simple container for managing entity groups.*/
public class Entities{
private static final Array<EntityGroup<?>> groupArray = new Array<>();
private static final IntMap<EntityGroup<?>> groups = new IntMap<>();
private static final Rectangle viewport = new Rectangle();
private static final boolean clip = true;
private static int count = 0;
private final Array<EntityGroup<?>> groupArray = new Array<>();
public static void clear(){
public void clear(){
for(EntityGroup group : groupArray){
group.clear();
}
}
public static EntityGroup<?> getGroup(int id){
return groups.get(id);
public EntityGroup<?> get(int id){
return groupArray.get(id);
}
public static Array<EntityGroup<?>> getAllGroups(){
public Array<EntityGroup<?>> all(){
return groupArray;
}
public static <T extends Entity> EntityGroup<T> addGroup(Class<T> type){
return addGroup(type, true);
public <T extends Entity> EntityGroup<T> add(Class<T> type){
return add(type, true);
}
public static <T extends Entity> EntityGroup<T> addGroup(Class<T> type, boolean useTree){
EntityGroup<T> group = new EntityGroup<>(type, useTree);
groups.put(group.getID(), group);
public <T extends Entity> EntityGroup<T> add(Class<T> type, boolean useTree){
EntityGroup<T> group = new EntityGroup<>(groupArray.size, type, useTree);
groupArray.add(group);
return group;
}
public static void update(EntityGroup<?> group){
group.updateEvents();
if(group.useTree()){
collisions.updatePhysics(group);
}
for(Entity e : group.all()){
e.update();
}
}
public static int countInBounds(EntityGroup<?> group){
count = 0;
draw(group, e -> true, e -> count++);
return count;
}
public static void draw(EntityGroup<?> group){
draw(group, e -> true);
}
public static <T extends DrawTrait> void draw(EntityGroup<?> group, Predicate<T> toDraw){
draw(group, toDraw, DrawTrait::draw);
}
@SuppressWarnings("unchecked")
public static <T extends DrawTrait> void draw(EntityGroup<?> group, Predicate<T> toDraw, Consumer<T> cons){
if(clip){
Camera cam = Core.camera;
viewport.set(cam.position.x - cam.width / 2, cam.position.y - cam.height / 2, cam.width, cam.height);
}
for(Entity e : group.all()){
if(!(e instanceof DrawTrait) || !toDraw.test((T)e) || !e.isAdded()) continue;
DrawTrait draw = (DrawTrait)e;
if(!clip || viewport.overlaps(draw.getX() - draw.drawSize()/2f, draw.getY() - draw.drawSize()/2f, draw.drawSize(), draw.drawSize())){
cons.accept((T)e);
}
}
}
}

View File

@@ -1,30 +1,34 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntMap;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.math.geom.QuadTree;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.traits.Entity;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.math.geom.*;
import io.anuke.mindustry.entities.traits.*;
import static io.anuke.mindustry.Vars.collisions;
/** Represents a group of a certain type of entity.*/
@SuppressWarnings("unchecked")
public class EntityGroup<T extends Entity>{
private static int lastid;
private final boolean useTree;
private final int id;
private final Class<T> type;
private final Array<T> entityArray = new Array<>(false, 16);
private final Array<T> entitiesToRemove = new Array<>(false, 16);
private final Array<T> entitiesToAdd = new Array<>(false, 16);
private final Array<T> entityArray = new Array<>(false, 32);
private final Array<T> entitiesToRemove = new Array<>(false, 32);
private final Array<T> entitiesToAdd = new Array<>(false, 32);
private IntMap<T> map;
private QuadTree tree;
private Consumer<T> removeListener;
private Consumer<T> addListener;
public EntityGroup(Class<T> type, boolean useTree){
private final Rectangle viewport = new Rectangle();
private int count = 0;
public EntityGroup(int id, Class<T> type, boolean useTree){
this.useTree = useTree;
this.id = lastid++;
this.id = id;
this.type = type;
if(useTree){
@@ -32,6 +36,46 @@ public class EntityGroup<T extends Entity>{
}
}
public void update(){
updateEvents();
if(useTree()){
collisions.updatePhysics(this);
}
for(Entity e : all()){
e.update();
}
}
public int countInBounds(){
count = 0;
draw(e -> true, e -> count++);
return count;
}
public void draw(){
draw(e -> true);
}
public void draw(Predicate<T> toDraw){
draw(toDraw, t -> ((DrawTrait)t).draw());
}
public void draw(Predicate<T> toDraw, Consumer<T> cons){
Camera cam = Core.camera;
viewport.set(cam.position.x - cam.width / 2, cam.position.y - cam.height / 2, cam.width, cam.height);
for(Entity e : all()){
if(!(e instanceof DrawTrait) || !toDraw.test((T)e) || !e.isAdded()) continue;
DrawTrait draw = (DrawTrait)e;
if(viewport.overlaps(draw.getX() - draw.drawSize()/2f, draw.getY() - draw.drawSize()/2f, draw.drawSize(), draw.drawSize())){
cons.accept((T)e);
}
}
}
public boolean useTree(){
return useTree;
}

View File

@@ -68,13 +68,13 @@ public class Units{
/** Returns the neareset damaged tile. */
public static TileEntity findDamagedTile(Team team, float x, float y){
Tile tile = Geometry.findClosest(x, y, world.indexer.getDamaged(team));
Tile tile = Geometry.findClosest(x, y, indexer.getDamaged(team));
return tile == null ? null : tile.entity;
}
/** Returns the neareset ally tile in a range. */
public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
return world.indexer.findTile(team, x, y, range, pred);
return indexer.findTile(team, x, y, range, pred);
}
/** Returns the neareset enemy tile in a range. */
@@ -82,7 +82,7 @@ public class Units{
if(team == Team.derelict) return null;
for(Team enemy : state.teams.enemiesOf(team)){
TileEntity entity = world.indexer.findTile(enemy, x, y, range, pred);
TileEntity entity = indexer.findTile(enemy, x, y, range, pred);
if(entity != null){
return entity;
}

View File

@@ -20,6 +20,10 @@ public interface HealthTrait{
default void onDeath(){
}
default boolean damaged(){
return health() < maxHealth() - 0.0001f;
}
default void damage(float amount){
health(health() - amount);
if(health() <= 0 && !isDead()){

View File

@@ -16,6 +16,7 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.units.CommandCenter.*;
import io.anuke.mindustry.world.blocks.units.UnitFactory.*;
import io.anuke.mindustry.world.meta.*;
@@ -25,7 +26,6 @@ import static io.anuke.mindustry.Vars.*;
/** Base class for AI units. */
public abstract class BaseUnit extends Unit implements ShooterTrait{
protected static int timerIndex = 0;
protected static final int timerTarget = timerIndex++;
@@ -83,6 +83,22 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
return world.tile(spawner);
}
public boolean isCommanded(){
return indexer.getAllied(team, BlockFlag.comandCenter).size != 0 && indexer.getAllied(team, BlockFlag.comandCenter).first().entity instanceof CommandCenterEntity;
}
public UnitCommand getCommand(){
if(isCommanded()){
return indexer.getAllied(team, BlockFlag.comandCenter).first().<CommandCenterEntity>entity().command;
}
return null;
}
/**Called when a command is recieved from the command center.*/
public void onCommand(UnitCommand command){
}
/** Initialize the type and team of this unit. Only call once! */
public void init(UnitType type, Team team){
if(this.type != null) throw new RuntimeException("This unit is already initialized!");
@@ -129,12 +145,12 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
public void targetClosestAllyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, world.indexer.getAllied(team, flag));
Tile target = Geometry.findClosest(x, y, indexer.getAllied(team, flag));
if(target != null) this.target = target.entity;
}
public void targetClosestEnemyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, world.indexer.getEnemy(team, flag));
Tile target = Geometry.findClosest(x, y, indexer.getEnemy(team, flag));
if(target != null) this.target = target.entity;
}
@@ -303,6 +319,10 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
state.set(getStartState());
health(maxHealth());
if(isCommanded()){
onCommand(getCommand());
}
}
@Override

View File

@@ -1,20 +1,19 @@
package io.anuke.mindustry.entities.type;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Fill;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.mindustry.entities.Predict;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.meta.BlockFlag;
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.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
public abstract class FlyingUnit extends BaseUnit{
protected float[] weaponAngles = {0, 0};
@@ -80,14 +79,40 @@ public abstract class FlyingUnit extends BaseUnit{
return;
}
target = getClosestCore();
};
target = getSpawner();
if(target == null) target = getClosestCore();
}
if(target != null){
circle(60f + Mathf.absin(Time.time() + Mathf.randomSeed(id) * 1200f, 70f, 1200f));
circle(80f + Mathf.randomSeed(id) * 120);
}
}
};
},
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(retarget()){
target = getSpawner();
Tile repair = Geometry.findClosest(x, y, indexer.getAllied(team, BlockFlag.repair));
if(repair != null && damaged()) FlyingUnit.this.target = repair.entity;
if(target == null) target = getClosestCore();
}
circle(targetHasFlag(BlockFlag.repair) ? 20f : 60f + Mathf.randomSeed(id) * 50, 0.65f * type.speed);
}
};;
@Override
public void onCommand(UnitCommand command){
state.set(command == UnitCommand.retreat ? retreat :
command == UnitCommand.attack ? attack :
command == UnitCommand.patrol ? patrol :
null);
}
@Override
public void move(float x, float y){

View File

@@ -1,22 +1,20 @@
package io.anuke.mindustry.entities.type;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Predict;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.type.Weapon;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
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.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public abstract class GroundUnit extends BaseUnit{
protected static Vector2 vec = new Vector2();
@@ -63,8 +61,25 @@ public abstract class GroundUnit extends BaseUnit{
}
}
}
},
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
moveAwayFromCore();
}
};
@Override
public void onCommand(UnitCommand command){
state.set(command == UnitCommand.retreat ? retreat :
command == UnitCommand.attack ? attack :
command == UnitCommand.patrol ? patrol :
null);
}
@Override
public void interpolate(){
super.interpolate();
@@ -182,9 +197,9 @@ public abstract class GroundUnit extends BaseUnit{
protected void patrol(){
vec.trns(baseRotation, type.speed * Time.delta());
velocity.add(vec.x, vec.y);
vec.trns(baseRotation, type.hitsizeTile * 3);
vec.trns(baseRotation, type.hitsizeTile * 5);
Tile tile = world.tileWorld(x + vec.x, y + vec.y);
if((tile == null || tile.solid() || tile.floor().drownTime > 0) || stuckTime > 10f){
if((tile == null || tile.solid() || tile.floor().drownTime > 0 || tile.floor().isLiquid) || stuckTime > 10f){
baseRotation += Mathf.sign(id % 2 - 0.5f) * Time.delta() * 3f;
}
@@ -208,7 +223,7 @@ public abstract class GroundUnit extends BaseUnit{
protected void moveToCore(){
Tile tile = world.tileWorld(x, y);
if(tile == null) return;
Tile targetTile = world.pathfinder.getTargetTile(team, tile);
Tile targetTile = pathfinder.getTargetTile(team, tile);
if(tile == targetTile) return;
@@ -231,10 +246,10 @@ public abstract class GroundUnit extends BaseUnit{
Tile tile = world.tileWorld(x, y);
if(tile == null) return;
Tile targetTile = world.pathfinder.getTargetTile(enemy, tile);
Tile targetTile = pathfinder.getTargetTile(enemy, tile);
TileEntity core = getClosestCore();
if(tile == targetTile || core == null || dst(core) < 90f) return;
if(tile == targetTile || core == null || dst(core) < 120f) return;
velocity.add(vec.trns(angleTo(targetTile), type.speed * Time.delta()));
rotation = Mathf.slerpDelta(rotation, baseRotation, type.rotatespeed);

View File

@@ -14,6 +14,7 @@ import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*;
@@ -731,7 +732,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
target = Units.closestTarget(team, x, y, getWeapon().bullet.range(), u -> u.getTeam() != Team.derelict, u -> u.getTeam() != Team.derelict);
if(mech.canHeal && target == null){
target = Geometry.findClosest(x, y, world.indexer.getDamaged(Team.sharded));
target = Geometry.findClosest(x, y, indexer.getDamaged(Team.sharded));
if(target != null && dst(target) > getWeapon().bullet.range()){
target = null;
}else if(target != null){
@@ -774,6 +775,31 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
//region utility methods
public void sendMessage(String text){
if(isLocal){
if(Vars.ui != null){
Log.info("add " + text);
Vars.ui.chatfrag.addMessage(text, null);
}
}else{
Call.sendMessage(con.id, text, null, null);
}
}
public void sendMessage(String text, Player from){
sendMessage(text, from, NetClient.colorizeName(from.id, from.name));
}
public void sendMessage(String text, Player from, String fromName){
if(isLocal){
if(Vars.ui != null){
Vars.ui.chatfrag.addMessage(text, fromName);
}
}else{
Call.sendMessage(con.id, text, fromName, from);
}
}
/** Resets all values of the player. */
public void reset(){
resetNoAdd();

View File

@@ -52,7 +52,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
tile.entity.health = health;
if(tile.entity.damaged()){
world.indexer.notifyTileDamaged(tile.entity);
indexer.notifyTileDamaged(tile.entity);
}
}
}
@@ -171,14 +171,10 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
}
if(preHealth >= maxHealth() - 0.00001f && health < maxHealth() && world != null){ //when just damaged
world.indexer.notifyTileDamaged(this);
indexer.notifyTileDamaged(this);
}
}
public boolean damaged(){
return health < maxHealth() - 0.00001f;
}
public Tile getTile(){
return tile;
}

View File

@@ -256,7 +256,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
//apply knockback based on spawns
if(getTeam() != waveTeam){
float relativeSize = state.rules.dropZoneRadius + getSize()/2f + 1f;
for(Tile spawn : world.spawner.getGroundSpawns()){
for(Tile spawn : spawner.getGroundSpawns()){
if(withinDst(spawn.worldx(), spawn.worldy(), relativeSize)){
velocity.add(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta()));
}

View File

@@ -3,11 +3,11 @@ 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.UnitState;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public abstract class BaseDrone extends FlyingUnit{
public final UnitState retreat = new UnitState(){
@@ -20,7 +20,7 @@ public abstract class BaseDrone extends FlyingUnit{
state.set(getStartState());
}else if(!targetHasFlag(BlockFlag.repair)){
if(retarget()){
Tile repairPoint = Geometry.findClosest(x, y, world.indexer.getAllied(team, BlockFlag.repair));
Tile repairPoint = Geometry.findClosest(x, y, indexer.getAllied(team, BlockFlag.repair));
if(repairPoint != null){
target = repairPoint;
}else{
@@ -33,6 +33,11 @@ public abstract class BaseDrone extends FlyingUnit{
}
};
@Override
public void onCommand(UnitCommand command){
//do nothing, normal commands are not applicable here
}
@Override
protected void updateRotation(){
if(target != null && shouldRotate() && target.dst(this) < type.range){
@@ -44,7 +49,7 @@ public abstract class BaseDrone extends FlyingUnit{
@Override
public void behavior(){
if(health <= maxHealth() * type.retreatPercent && !state.is(retreat) && Geometry.findClosest(x, y, world.indexer.getAllied(team, BlockFlag.repair)) != null){
if(health <= maxHealth() * type.retreatPercent && !state.is(retreat) && Geometry.findClosest(x, y, indexer.getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
}
}

View File

@@ -14,7 +14,7 @@ import io.anuke.mindustry.world.Tile;
import java.io.*;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
/** A drone that only mines.*/
public class MinerDrone extends BaseDrone implements MinerTrait{
@@ -46,7 +46,7 @@ public class MinerDrone extends BaseDrone implements MinerTrait{
setState(drop);
}else{
if(retarget() && targetItem != null){
target = world.indexer.findClosestOre(x, y, targetItem);
target = indexer.findClosestOre(x, y, targetItem);
}
if(target instanceof Tile){
@@ -174,6 +174,6 @@ public class MinerDrone extends BaseDrone implements MinerTrait{
if(entity == null){
return;
}
targetItem = Structs.findMin(type.toMine, world.indexer::hasOre, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b)));
targetItem = Structs.findMin(type.toMine, indexer::hasOre, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b)));
}
}

View File

@@ -0,0 +1,18 @@
package io.anuke.mindustry.entities.units;
import io.anuke.arc.*;
public enum UnitCommand{
attack, retreat, patrol;
private final String localized;
public static final UnitCommand[] all = values();
UnitCommand(){
localized = Core.bundle.get("command." + name());
}
public String localized(){
return localized;
}
}

View File

@@ -19,7 +19,7 @@ public abstract class Content{
*/
public abstract ContentType getContentType();
/** Called after all content is created. Do not use to load regions or texture data! */
/** Called after all content and modules are created. Do not use to load regions or texture data! */
public void init(){
}

View File

@@ -27,8 +27,8 @@ public class EventType{
}
}
/** Called when the game is first loaded. */
public static class GameLoadEvent{
/** Called when the client game is first loaded. */
public static class ClientLoadEvent{
}

View File

@@ -20,7 +20,6 @@ public enum Gamemode{
rules.respawnTime = 0f;
}),
attack(rules -> {
rules.enemyCheat = true;
rules.unitDrops = true;
rules.attackMode = true;
rules.waves = true;

View File

@@ -2,11 +2,16 @@ package io.anuke.mindustry.game;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.type.*;
import java.io.*;
import java.util.zip.*;
import static io.anuke.mindustry.Vars.*;
/** Stores player unlocks. Clientside only. */
@@ -29,6 +34,43 @@ public class GlobalData{
});
}
public void exportData(FileHandle file) throws IOException{
Array<FileHandle> files = new Array<>();
files.add(Core.settings.getSettingsFile());
files.addAll(customMapDirectory.list());
files.addAll(saveDirectory.list());
String base = Core.settings.getDataDirectory().path();
try(OutputStream fos = file.write(false, 2048); ZipOutputStream zos = new ZipOutputStream(fos)){
for(FileHandle add : files){
zos.putNextEntry(new ZipEntry(add.path().substring(base.length())));
Streams.copyStream(add.read(), zos);
zos.closeEntry();
}
}
}
public void importData(FileHandle file){
FileHandle zipped = new ZipFileHandle(file);
FileHandle base = Core.settings.getDataDirectory();
if(!base.child("settings.bin").exists()){
throw new IllegalArgumentException("Not valid save data.");
}
//purge existing data
for(FileHandle f : base.list()){
if(f.isDirectory()){
f.deleteDirectory();
}else{
f.delete();
}
}
zipped.walk(f -> f.copyTo(base.child(f.path())));
}
public void modified(){
modified = true;
}

View File

@@ -14,15 +14,12 @@ import static io.anuke.mindustry.Vars.*;
/** Controls playback of multiple music tracks.*/
public class MusicControl{
private static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.45f, musicWaveChance = 0.35f;
private static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.5f, musicWaveChance = 0.4f;
/** normal, ambient music, plays at any time */
public final Array<Music> ambientMusic = Array.with(Musics.game1, Musics.game3, Musics.game4, Musics.game6);
/** darker music, used in times of conflict */
public final Array<Music> darkMusic = Array.with(Musics.game2, Musics.game5, Musics.game7);
/** all music, both dark and ambient */
public final Array<Music> allMusic = Array.withArrays(ambientMusic, darkMusic);
private Music lastRandomPlayed;
private Interval timer = new Interval();
private @Nullable Music current;
@@ -88,7 +85,7 @@ public class MusicControl{
}
//dark based on enemies
return Mathf.chance(state.enemies() / 70f);
return Mathf.chance(state.enemies() / 70f + 0.1f);
}
/** Plays and fades in a music track. This must be called every frame.

View File

@@ -161,7 +161,7 @@ public class Tutorial{
}
},
deposit(() -> event("deposit")),
waves(() -> state.wave > 2 && state.enemies() <= 0 && !world.spawner.isSpawning()){
waves(() -> state.wave > 2 && state.enemies() <= 0 && !spawner.isSpawning()){
void begin(){
state.rules.waveTimer = true;
logic.runWave();

View File

@@ -85,4 +85,6 @@ public class Drawf{
float oy = 17f / 63f * length;
Draw.rect(Core.atlas.find("shape-3"), x, y - oy + length / 2f, width, length, width / 2f, oy, rotation - 90);
}
}

View File

@@ -22,7 +22,7 @@ public class FloorRenderer implements Disposable{
private final static int chunksize = 64;
private Chunk[][] cache;
private CacheBatch cbatch;
private MultiCacheBatch cbatch;
private IntSet drawnLayerSet = new IntSet();
private IntArray drawnLayers = new IntArray();
private ObjectSet<CacheLayer> used = new ObjectSet<>();
@@ -185,7 +185,7 @@ public class FloorRenderer implements Disposable{
floor = tile.floor();
}
if(tile.block().cacheLayer == layer && layer == CacheLayer.walls){
if(tile.block().cacheLayer == layer && layer == CacheLayer.walls && !(tile.isDarkened() && tile.rotation() >= 5)){
tile.block().draw(tile);
}else if(floor.cacheLayer == layer && (world.isAccessible(tile.x, tile.y) || tile.block().cacheLayer != CacheLayer.walls || !tile.block().fillsTile)){
floor.draw(tile);
@@ -204,8 +204,7 @@ public class FloorRenderer implements Disposable{
int chunksx = Mathf.ceil((float)(world.width()) / chunksize),
chunksy = Mathf.ceil((float)(world.height()) / chunksize);
cache = new Chunk[chunksx][chunksy];
SpriteCache sprites = new SpriteCache(world.width() * world.height() * 6, (world.width() / chunksize) * (world.height() / chunksize) * 2, false);
cbatch = new CacheBatch(sprites);
cbatch = new MultiCacheBatch(chunksize * chunksize * 4);
Time.mark();

View File

@@ -96,7 +96,7 @@ public class OverlayRenderer{
Lines.stroke(2f);
Draw.color(Color.GRAY, Color.LIGHT_GRAY, Mathf.absin(Time.time(), 8f, 1f));
for(Tile tile : world.spawner.getGroundSpawns()){
for(Tile tile : spawner.getGroundSpawns()){
if(tile.withinDst(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){
Draw.alpha(Mathf.clamp(1f - (player.dst(tile) - state.rules.dropZoneRadius) / spawnerMargin));
Lines.dashCircle(tile.worldx(), tile.worldy(), state.rules.dropZoneRadius);

View File

@@ -55,7 +55,7 @@ public class Pixelator implements Disposable{
Draw.rect(Draw.wrap(buffer.getTexture()), Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height);
Draw.blend();
renderer.draw(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName);
playerGroup.draw(p -> !p.isDead() && !p.isLocal, Player::drawName);
Core.camera.position.set(px, py);
Core.settings.put("animatedwater", hadWater);

View File

@@ -1,5 +1,6 @@
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;
@@ -10,7 +11,7 @@ import io.anuke.arc.util.Time;
public class Shaders{
public static Shadow shadow;
public static BlockBuild blockbuild;
public static Shield shield;
public static @Nullable Shield shield;
public static UnitBuild build;
public static FogShader fog;
public static MenuShader menu;
@@ -19,7 +20,13 @@ public class Shaders{
public static void init(){
shadow = new Shadow();
blockbuild = new BlockBuild();
shield = new Shield();
try{
shield = new Shield();
}catch(Throwable t){
//don't load shield shader
shield = null;
t.printStackTrace();
}
build = new UnitBuild();
fog = new FogShader();
menu = new MenuShader();

View File

@@ -21,6 +21,7 @@ public enum Binding implements KeyBind{
zoom_hold(KeyCode.CONTROL_LEFT, "view"),
zoom(new Axis(KeyCode.SCROLL)),
menu(Core.app.getType() == ApplicationType.Android ? KeyCode.BACK : KeyCode.ESCAPE),
fullscreen(KeyCode.F11),
pause(KeyCode.SPACE),
minimap(KeyCode.M),
toggle_menus(KeyCode.C),

View File

@@ -33,7 +33,7 @@ public class MobileInput extends InputHandler implements GestureListener{
//gesture data
private Vector2 vector = new Vector2();
private float lastDistance = -1f;
private float lastZoom = -1;
/** Position where the player started dragging a line. */
private int lineStartX, lineStartY;
@@ -469,6 +469,7 @@ public class MobileInput extends InputHandler implements GestureListener{
@Override
public boolean touchUp(int screenX, int screenY, int pointer, KeyCode button){
lastZoom = renderer.getScale();
//place down a line if in line mode
if(lineMode){
@@ -728,11 +729,11 @@ public class MobileInput extends InputHandler implements GestureListener{
@Override
public boolean zoom(float initialDistance, float distance){
if(Core.settings.getBool("keyboard")) return false;
if(lastDistance == -1) lastDistance = initialDistance;
if(lastZoom < 0){
lastZoom = renderer.getScale();
}
float amount = (Mathf.sign(distance > lastDistance) * 0.04f) * Time.delta();
renderer.scaleCamera(UnitScl.dp.scl(amount));
lastDistance = distance;
renderer.setScale(distance / initialDistance * lastZoom);
return true;
}

View File

@@ -1,63 +0,0 @@
package io.anuke.mindustry.io;
import io.anuke.arc.Core;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.util.*;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.input.Binding;
import java.util.Locale;
import static io.anuke.mindustry.Vars.headless;
public class BundleLoader{
public static void load(){
Core.settings.defaults("locale", "default");
Core.keybinds.setDefaults(Binding.values());
Core.settings.load();
loadBundle();
}
private static Locale getLocale(){
String loc = Core.settings.getString("locale");
if(loc.equals("default")){
return Locale.getDefault();
}else{
Locale lastLocale;
if(loc.contains("_")){
String[] split = loc.split("_");
lastLocale = new Locale(split[0], split[1]);
}else{
lastLocale = new Locale(loc);
}
return lastLocale;
}
}
private static void loadBundle(){
if(headless) return;
try{
//try loading external bundle
FileHandle handle = Core.files.local("bundle");
Locale locale = Locale.ENGLISH;
Core.bundle = I18NBundle.createBundle(handle, locale);
Log.info("NOTE: external translation bundle has been loaded.");
if(!headless){
Time.run(10f, () -> Vars.ui.showInfo("Note: You have successfully loaded an external translation bundle."));
}
}catch(Throwable e){
//no external bundle found
FileHandle handle = Core.files.internal("bundles/bundle");
Locale locale = getLocale();
Locale.setDefault(locale);
Core.bundle = I18NBundle.createBundle(handle, locale);
}
}
}

View File

@@ -1,10 +1,10 @@
package io.anuke.mindustry.io;
import io.anuke.arc.collection.*;
import io.anuke.mindustry.game.Rules;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.maps.*;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.maps;
public class SaveMeta{
public int version;
@@ -21,7 +21,7 @@ public class SaveMeta{
this.build = build;
this.timestamp = timestamp;
this.timePlayed = timePlayed;
this.map = world.maps.all().find(m -> m.name().equals(map));
this.map = maps.all().find(m -> m.name().equals(map));
this.wave = wave;
this.rules = rules;
this.tags = tags;

View File

@@ -81,7 +81,7 @@ public abstract class SaveVersion extends SaveFileReader{
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
lastReadBuild = map.getInt("build", -1);
Map worldmap = world.maps.byName(map.get("mapname", "\\\\\\"));
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
world.setMap(worldmap == null ? new Map(StringMap.of(
"name", map.get("mapname", "Unknown"),
"width", 1,
@@ -209,7 +209,7 @@ public abstract class SaveVersion extends SaveFileReader{
//write entity chunk
int groups = 0;
for(EntityGroup<?> group : Entities.getAllGroups()){
for(EntityGroup<?> group : entities.all()){
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
groups++;
}
@@ -217,7 +217,7 @@ public abstract class SaveVersion extends SaveFileReader{
stream.writeByte(groups);
for(EntityGroup<?> group : Entities.getAllGroups()){
for(EntityGroup<?> group : entities.all()){
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
stream.writeInt(group.size());
for(Entity entity : group.all()){

View File

@@ -5,12 +5,12 @@ import io.anuke.annotations.Annotations.WriteClass;
import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.ShooterTrait;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.net.Administration.TraceInfo;
import io.anuke.mindustry.net.Packets.AdminAction;
@@ -57,7 +57,7 @@ public class TypeIO{
byte gid = buffer.get();
if(gid == -1) return null;
int id = buffer.getInt();
return (Unit)Entities.getGroup(gid).getByID(id);
return (Unit)entities.get(gid).getByID(id);
}
@WriteClass(ShooterTrait.class)
@@ -70,7 +70,7 @@ public class TypeIO{
public static ShooterTrait readShooter(ByteBuffer buffer){
byte gid = buffer.get();
int id = buffer.getInt();
return (ShooterTrait)Entities.getGroup(gid).getByID(id);
return (ShooterTrait)entities.get(gid).getByID(id);
}
@WriteClass(Bullet.class)
@@ -177,6 +177,16 @@ public class TypeIO{
return Team.all[buffer.get()];
}
@WriteClass(UnitCommand.class)
public static void writeUnitCommand(ByteBuffer buffer, UnitCommand reason){
buffer.put((byte)reason.ordinal());
}
@ReadClass(UnitCommand.class)
public static UnitCommand readUnitCommand(ByteBuffer buffer){
return UnitCommand.all[buffer.get()];
}
@WriteClass(AdminAction.class)
public static void writeAction(ByteBuffer buffer, AdminAction reason){
buffer.put((byte)reason.ordinal());

View File

@@ -9,7 +9,7 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.filters.*;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.maps;
public class Map implements Comparable<Map>{
/** Whether this is a custom map. */
@@ -57,6 +57,14 @@ public class Map implements Comparable<Map>{
return Core.settings.getInt("hiscore" + file.nameWithoutExtension(), 0);
}
public FileHandle previewFile(){
return Vars.mapPreviewDirectory.child(file.nameWithoutExtension() + ".png");
}
public FileHandle cacheFile(){
return Vars.mapPreviewDirectory.child(file.nameWithoutExtension() + "-cache.dat");
}
public void setHighScore(int score){
Core.settings.put("hiscore" + file.nameWithoutExtension(), score);
Vars.data.modified();
@@ -94,7 +102,7 @@ public class Map implements Comparable<Map>{
if(tags.getInt("build", -1) < 83 && tags.getInt("build", -1) != -1 && tags.get("genfilters", "").isEmpty()){
return Array.with();
}
return world.maps.readFilters(tags.get("genfilters", ""));
return maps.readFilters(tags.get("genfilters", ""));
}
public String author(){
@@ -120,11 +128,11 @@ public class Map implements Comparable<Map>{
@Override
public int compareTo(Map map){
int type = -Boolean.compare(custom, map.custom);
if(type != 0){
return type;
}else{
return name().compareTo(map.name());
}
if(type != 0) return type;
int modes = Boolean.compare(Gamemode.pvp.valid(this), Gamemode.pvp.valid(map));
if(modes != 0) return modes;
return name().compareTo(map.name());
}
@Override

View File

@@ -0,0 +1,40 @@
package io.anuke.mindustry.maps;
import io.anuke.arc.assets.*;
import io.anuke.arc.assets.loaders.*;
import io.anuke.arc.assets.loaders.resolvers.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.*;
public class MapPreviewLoader extends TextureLoader{
public MapPreviewLoader(){
super(new AbsoluteFileHandleResolver());
}
@Override
public void loadAsync(AssetManager manager, String fileName, FileHandle file, TextureParameter parameter){
try{
super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter);
}catch(Exception e){
e.printStackTrace();
MapPreviewParameter param = (MapPreviewParameter)parameter;
Vars.maps.createNewPreview(param.map);
}
}
@Override
public Array<AssetDescriptor> getDependencies(String fileName, FileHandle file, TextureParameter parameter){
return Array.with(new AssetDescriptor<>("contentcreate", Content.class));
}
public static class MapPreviewParameter extends TextureParameter{
public Map map;
public MapPreviewParameter(Map map){
this.map = map;
}
}
}

View File

@@ -1,15 +1,22 @@
package io.anuke.mindustry.maps;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.assets.loaders.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.collection.IntSet.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.async.*;
import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.MapPreviewLoader.*;
import io.anuke.mindustry.maps.filters.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.storage.*;
@@ -18,7 +25,7 @@ import java.io.*;
import static io.anuke.mindustry.Vars.*;
public class Maps implements Disposable{
public class Maps{
/** List of all built-in maps. Filenames only. */
private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "veins", "glacier"};
/** All maps stored in an ordered array. */
@@ -26,6 +33,8 @@ public class Maps implements Disposable{
/** Serializer for meta. */
private Json json = new Json();
private AsyncExecutor executor = new AsyncExecutor(2);
/** Returns a list of all maps, including custom ones. */
public Array<Map> all(){
return maps;
@@ -45,6 +54,12 @@ public class Maps implements Disposable{
return maps.find(m -> m.name().equals(name));
}
public Maps(){
Events.on(ClientLoadEvent.class, event -> {
maps.sort();
});
}
/**
* Loads a map from the map folder and returns it. Should only be used for zone maps.
* Does not add this map to the map list.
@@ -74,7 +89,13 @@ public class Maps implements Disposable{
}
public void reload(){
dispose();
for(Map map : maps){
if(map.texture != null){
map.texture.dispose();
map.texture = null;
}
}
maps.clear();
load();
}
@@ -128,7 +149,10 @@ public class Maps implements Disposable{
}
}
map.texture = new Texture(MapIO.generatePreview(world.getTiles()));
Pixmap pix = MapIO.generatePreview(world.getTiles());
executor.submit(() -> map.previewFile().writePNG(pix));
map.texture = new Texture(pix);
}
maps.add(map);
maps.sort();
@@ -227,8 +251,8 @@ public class Maps implements Disposable{
int index = 0;
for(Block block : new Block[]{Blocks.oreCopper, Blocks.oreLead, Blocks.oreCoal, Blocks.oreTitanium, Blocks.oreThorium}){
OreFilter filter = new OreFilter();
filter.threshold += index ++ * 0.019f;
filter.scl += index/2f;
filter.threshold += index ++ * 0.018f;
filter.scl += index/2.1f;
filter.ore = block;
filters.add(filter);
}
@@ -284,6 +308,70 @@ public class Maps implements Disposable{
}
}
public void loadPreviews(){
Array<Map> createNew = new Array<>();
for(Map map : maps){
//try to load preview
if(map.previewFile().exists()){
//this may fail, but calls createNewPreview
Core.assets.load(new AssetDescriptor<>(map.previewFile().path() + "." + mapExtension, Texture.class, new MapPreviewParameter(map))).loaded = t -> map.texture = (Texture)t;
}else{
createNew.add(map);
}
try{
readCache(map);
}catch(Exception ignored){
}
}
((CustomLoader)Core.assets.getLoader(Content.class)).loaded = () -> Core.app.post(() -> createNew.each(this::createNewPreview));
}
public void createNewPreview(Map map){
try{
//if it's here, then the preview failed to load or doesn't exist, make it
//this has to be done synchronously!
Pixmap pix = MapIO.generatePreview(map);
Core.app.post(() -> map.texture = new Texture(pix));
executor.submit(() -> {
try{
map.previewFile().writePNG(pix);
writeCache(map);
}catch(Exception e){
e.printStackTrace();
}
});
}catch(IOException e){
Log.err("Failed to generate preview!", e);
Core.app.post(() -> map.texture = new Texture("sprites/error.png"));
}
}
private void writeCache(Map map) throws IOException{
try(DataOutputStream stream = new DataOutputStream(map.cacheFile().write(false, Streams.DEFAULT_BUFFER_SIZE))){
stream.write(0);
stream.writeInt(map.spawns);
stream.write(map.teams.size);
IntSetIterator iter = map.teams.iterator();
while(iter.hasNext){
stream.write(iter.next());
}
}
}
private void readCache(Map map) throws IOException{
try(DataInputStream stream = new DataInputStream(map.cacheFile().read(Streams.DEFAULT_BUFFER_SIZE))){
stream.read(); //version
map.spawns = stream.readInt();
int teamsize = stream.readByte();
for(int i = 0; i < teamsize; i++){
map.teams.add(stream.read());
}
}
}
/** Find a new filename to put a map to. */
private FileHandle findFile(){
//find a map name that isn't used.
@@ -301,12 +389,8 @@ public class Maps implements Disposable{
throw new IOException("Map name cannot be empty! File: " + file);
}
if(!headless){
map.texture = new Texture(MapIO.generatePreview(map));
}
maps.add(map);
//maps.sort();
maps.sort();
}
private void loadCustomMaps(){
@@ -321,15 +405,4 @@ public class Maps implements Disposable{
}
}
}
@Override
public void dispose(){
for(Map map : maps){
if(map.texture != null){
map.texture.dispose();
map.texture = null;
}
}
maps.clear();
}
}

View File

@@ -33,7 +33,9 @@ public class MirrorFilter extends GenerateFilter{
mirror(v3, v1.x, v1.y, v2.x, v2.y);
Tile tile = in.tile(v3.x, v3.y);
in.floor = tile.floor();
in.block = tile.block();
if(!tile.block().synthetic()){
in.block = tile.block();
}
in.ore = tile.overlay();
}
}

View File

@@ -8,7 +8,7 @@ import static io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import static io.anuke.mindustry.maps.filters.FilterOption.oresOnly;
public class OreFilter extends GenerateFilter{
public float scl = 23, threshold = 0.811f, octaves = 2f, falloff = 0.3f;
public float scl = 23, threshold = 0.81f, octaves = 2f, falloff = 0.3f;
public Block ore = Blocks.oreCopper;
{

View File

@@ -51,7 +51,7 @@ public class MapGenerator extends Generator{
@Override
public void init(Loadout loadout){
this.loadout = loadout;
map = world.maps.loadInternalMap(mapName);
map = maps.loadInternalMap(mapName);
width = map.width;
height = map.height;
}

View File

@@ -7,7 +7,6 @@ import io.anuke.arc.collection.*;
import static io.anuke.mindustry.Vars.headless;
public class Administration{
/** All player info. Maps UUIDs to info. This persists throughout restarts. */
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
private Array<String> bannedIPs = new Array<>();

View File

@@ -97,7 +97,7 @@ public class CrashSender{
ex(() -> value.addChild("server", new JsonValue(fs)));
ex(() -> value.addChild("players", new JsonValue(Vars.playerGroup.size())));
ex(() -> value.addChild("state", new JsonValue(Vars.state.getState().name())));
ex(() -> value.addChild("os", new JsonValue(System.getProperty("os.name"))));
ex(() -> value.addChild("os", new JsonValue(System.getProperty("os.name") + "x" + (OS.is64Bit ? "64" : "32"))));
ex(() -> value.addChild("trace", new JsonValue(parseException(exception))));
boolean[] sent = {false};

View File

@@ -1,19 +1,18 @@
package io.anuke.mindustry.net;
import io.anuke.arc.Core;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.BiConsumer;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.gen.Call;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.net.Streamable.StreamBuilder;
import io.anuke.mindustry.net.Streamable.*;
import net.jpountz.lz4.*;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.io.*;
import java.nio.*;
import static io.anuke.mindustry.Vars.*;
@@ -28,6 +27,8 @@ public class Net{
private static ClientProvider clientProvider;
private static ServerProvider serverProvider;
private static IntMap<StreamBuilder> streams = new IntMap<>();
private static final LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor();
private static final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor();
/** Display a network error. Call on the graphics thread. */
public static void showError(Throwable e){
@@ -149,11 +150,11 @@ public class Net{
}
public static byte[] compressSnapshot(byte[] input){
return serverProvider.compressSnapshot(input);
return compressor.compress(input);
}
public static byte[] decompressSnapshot(byte[] input, int size){
return clientProvider.decompressSnapshot(input, size);
return decompressor.decompress(input, size);
}
/**
@@ -359,9 +360,6 @@ public class Net{
/** Disconnect from the server. */
void disconnect();
/** Decompress an input snapshot byte array. */
byte[] decompressSnapshot(byte[] input, int size);
/**
* Discover servers. This should run the callback regardless of whether any servers are found. Should not block.
* Callback should be run on libGDX main thread.
@@ -435,9 +433,6 @@ public class Net{
/** Close the server connection. */
void close();
/** Compress an input snapshot byte array. */
byte[] compressSnapshot(byte[] input);
/** Return all connected users. */
Iterable<? extends NetConnection> getConnections();

View File

@@ -45,7 +45,7 @@ public class NetworkIO{
state.wave = stream.readInt();
state.wavetime = stream.readFloat();
Entities.clear();
entities.clear();
int id = stream.readInt();
player.resetNoAdd();
player.read(stream);

View File

@@ -14,7 +14,7 @@ public class Packets{
public enum KickReason{
kick, clientOutdated, serverOutdated, banned, gameover(true), recentKick,
nameInUse, idInUse, nameEmpty, customClient, serverClose;
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch;
public final boolean quiet;

View File

@@ -0,0 +1,21 @@
package io.anuke.mindustry.plugin;
import io.anuke.arc.util.*;
public abstract class Plugin{
/** Called after all plugins have been created and commands have been registered.*/
public void init(){
}
/** Register any commands to be used on the server side, e.g. from the console. */
public void registerServerCommands(CommandHandler handler){
}
/** Register any commands to be used on the client side, e.g. sent from an in-game player.. */
public void registerClientCommands(CommandHandler handler){
}
}

View File

@@ -0,0 +1,82 @@
package io.anuke.mindustry.plugin;
import io.anuke.arc.collection.Array;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.io.*;
import java.lang.reflect.*;
import java.net.*;
import static io.anuke.mindustry.Vars.pluginDirectory;
public class Plugins{
private Array<LoadedPlugin> loaded = new Array<>();
/** 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 = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, jar.file().toURI().toURL());
Class<?> main = Class.forName(meta.main);
return new LoadedPlugin(jar, zip, (Plugin)main.newInstance(), meta);
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedPlugin{
public final FileHandle jarFile;
public final FileHandle zipRoot;
public final Plugin plugin;
public final PluginMeta meta;
public LoadedPlugin(FileHandle jarFile, FileHandle zipRoot, Plugin plugin, PluginMeta meta){
this.zipRoot = zipRoot;
this.jarFile = jarFile;
this.plugin = plugin;
this.meta = meta;
}
}
/** Plugin metadata information.*/
public static class PluginMeta{
public String name, author, main, description;
public String version;
}
}

View File

@@ -1,14 +1,13 @@
package io.anuke.mindustry.type;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
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.game.UnlockableContent;
import io.anuke.mindustry.ui.ContentDisplay;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
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.*;
import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.content;
@@ -40,6 +39,7 @@ 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++){

View File

@@ -3,7 +3,6 @@ package io.anuke.mindustry.type;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
@@ -28,7 +27,7 @@ public class Zone extends UnlockableContent{
public int configureWave = 15;
public int launchPeriod = 10;
public Loadout loadout = Loadouts.basicShard;
public Texture preview;
public TextureRegion preview;
protected ItemStack[] baseLaunchCost = {};
protected Array<ItemStack> startingItems = new Array<>();
@@ -41,6 +40,11 @@ public class Zone extends UnlockableContent{
this.generator = generator;
}
@Override
public void load(){
preview = Core.atlas.find(name);
}
public Rules getRules(){
if(generator instanceof MapGenerator){
return ((MapGenerator)generator).getMap().rules();
@@ -174,13 +178,6 @@ public class Zone extends UnlockableContent{
}
}
@Override
public void load(){
if(Core.files.internal("zones/" + name + ".png").exists() && !headless){
preview = new Texture(Core.files.internal("zones/" + name + ".png"));
}
}
@Override
public boolean alwaysUnlocked(){
return alwaysUnlocked;

View File

@@ -6,13 +6,12 @@ import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.ui.*;
import static io.anuke.mindustry.Vars.world;
public class CustomGameDialog extends FloatingDialog{
private MapPlayDialog dialog = new MapPlayDialog();
@@ -42,7 +41,7 @@ public class CustomGameDialog extends FloatingDialog{
int i = 0;
maps.defaults().width(170).fillY().top().pad(4f);
for(Map map : world.maps.all()){
for(Map map : Vars.maps.all()){
if(i % maxwidth == 0){
maps.row();
@@ -83,7 +82,7 @@ public class CustomGameDialog extends FloatingDialog{
i++;
}
if(world.maps.all().size == 0){
if(Vars.maps.all().size == 0){
maps.add("$maps.none").pad(50);
}

View File

@@ -14,9 +14,8 @@ import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.scene.utils.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.Saves.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.SaveIO.*;
@@ -33,13 +32,10 @@ public class DeployDialog extends FloatingDialog{
private ObjectSet<ZoneNode> nodes = new ObjectSet<>();
private ZoneInfoDialog info = new ZoneInfoDialog();
private Rectangle bounds = new Rectangle();
private Texture nomap = new Texture("zones/nomap.png");
public DeployDialog(){
super("", "fulldialog");
Events.on(DisposeEvent.class, e -> nomap.dispose());
ZoneNode root = new ZoneNode(Zones.groundZero, null);
TreeLayout layout = new TreeLayout();
@@ -90,6 +86,7 @@ public class DeployDialog extends FloatingDialog{
Stack sub = new Stack();
if(control.saves.getZoneSlot().getZone() != null){
sub.add(new Table(f -> f.margin(4f).add(new Image("whiteui")).color(Color.fromGray(0.1f)).grow()));
sub.add(new Table(f -> f.margin(4f).add(new Image(control.saves.getZoneSlot().getZone().preview).setScaling(Scaling.fit)).color(Color.DARK_GRAY).grow()));
}
@@ -195,7 +192,7 @@ public class DeployDialog extends FloatingDialog{
}
stack.setSize(Tmp.v1.x, Tmp.v1.y);
stack.add(new Table(t -> t.margin(4f).add(new Image(node.zone.preview != null ? node.zone.preview : nomap).setScaling(Scaling.stretch)).color(node.zone.unlocked() ? Color.DARK_GRAY : Color.fromGray(0.2f)).grow()));
stack.add(new Table(t -> t.margin(4f).add(new Image(node.zone.preview).setScaling(Scaling.stretch)).color(node.zone.unlocked() ? Color.DARK_GRAY : Color.fromGray(0.2f)).grow()));
stack.update(() -> stack.setPosition(node.x + panX + width / 2f, node.y + panY + height / 2f, Align.center));
Button button = new Button("square");

View File

@@ -51,6 +51,7 @@ public class LoadDialog extends FloatingDialog{
Time.runTask(2f, () -> Core.scene.setScrollFocus(pane));
Array<SaveSlot> array = control.saves.getSaveSlots();
array.sort((slot, other) -> -Long.compare(slot.getTimestamp(), other.getTimestamp()));
for(SaveSlot slot : array){
if(slot.isHidden()) continue;

View File

@@ -63,7 +63,7 @@ public class MapsDialog extends FloatingDialog{
if(!ios){
buttons.addImageTextButton("$editor.importmap", "icon-load", iconsize, () -> {
Platform.instance.showFileChooser("$editor.importmap", "Map File", file -> {
world.maps.tryCatchMapError(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showError("$editor.errorimage");
return;
@@ -73,14 +73,14 @@ public class MapsDialog extends FloatingDialog{
if(file.extension().equalsIgnoreCase(mapExtension)){
map = MapIO.createMap(file, true);
}else{
map = world.maps.makeLegacyMap(file);
map = maps.makeLegacyMap(file);
}
//when you attempt to import a save, it will have no name, so generate one
String name = map.tags.getOr("name", () -> {
String result = "unknown";
int number = 0;
while(world.maps.byName(result + number++) != null) ;
while(maps.byName(result + number++) != null) ;
return result + number;
});
@@ -90,19 +90,19 @@ public class MapsDialog extends FloatingDialog{
return;
}
Map conflict = world.maps.all().find(m -> m.name().equals(name));
Map conflict = maps.all().find(m -> m.name().equals(name));
if(conflict != null && !conflict.custom){
ui.showInfo(Core.bundle.format("editor.import.exists", name));
}else if(conflict != null){
ui.showConfirm("$confirm", "$editor.overwrite.confirm", () -> {
world.maps.tryCatchMapError(() -> {
world.maps.importMap(file);
maps.tryCatchMapError(() -> {
maps.importMap(file);
setup();
});
});
}else{
world.maps.importMap(map.file);
maps.importMap(map.file);
setup();
}
@@ -123,7 +123,7 @@ public class MapsDialog extends FloatingDialog{
float mapsize = 200f;
int i = 0;
for(Map map : world.maps.all()){
for(Map map : Vars.maps.all()){
if(i % maxwidth == 0){
maps.row();
@@ -143,7 +143,7 @@ public class MapsDialog extends FloatingDialog{
i++;
}
if(world.maps.all().size == 0){
if(Vars.maps.all().size == 0){
maps.add("$maps.none");
}
@@ -200,7 +200,7 @@ public class MapsDialog extends FloatingDialog{
table.addImageTextButton("$delete", "icon-trash-16-small", iconsizesmall, () -> {
ui.showConfirm("$confirm", Core.bundle.format("map.delete", map.name()), () -> {
world.maps.removeMap(map);
maps.removeMap(map);
dialog.hide();
setup();
});

View File

@@ -22,12 +22,13 @@ import static io.anuke.arc.Core.bundle;
import static io.anuke.mindustry.Vars.*;
public class SettingsMenuDialog extends SettingsDialog{
public SettingsTable graphics;
public SettingsTable game;
public SettingsTable sound;
private SettingsTable graphics;
private SettingsTable game;
private SettingsTable sound;
private Table prefs;
private Table menu;
private FloatingDialog dataDialog;
private boolean wasPaused;
public SettingsMenuDialog(){
@@ -75,6 +76,85 @@ public class SettingsMenuDialog extends SettingsDialog{
prefs.clearChildren();
prefs.add(menu);
dataDialog = new FloatingDialog("$settings.data");
dataDialog.addCloseButton();
dataDialog.cont.table("button", t -> {
t.defaults().size(240f, 60f).left();
String style = "clear";
t.addButton("$settings.cleardata", style, () -> ui.showConfirm("$confirm", "$settings.clearall.confirm", () -> {
ObjectMap<String, Object> map = new ObjectMap<>();
for(String value : Core.settings.keys()){
if(value.contains("usid") || value.contains("uuid")){
map.put(value, Core.settings.getString(value));
}
}
Core.settings.clear();
Core.settings.putAll(map);
Core.settings.save();
for(FileHandle file : dataDirectory.list()){
file.deleteDirectory();
}
Core.app.exit();
}));
t.row();
if(android && (Core.files.local("mindustry-maps").exists() || Core.files.local("mindustry-saves").exists())){
t.addButton("$classic.export", style, () -> {
control.checkClassicData();
});
}
t.row();
t.addButton("$data.export", style, () -> {
if(ios){
FileHandle file = Core.files.local("mindustry-data-export.zip");
try{
data.exportData(file);
}catch(Exception e){
ui.showError(Strings.parseException(e, true));
}
Platform.instance.shareFile(file);
}else{
Platform.instance.showFileChooser("$data.export", "Zip Files", file -> {
FileHandle ff = file;
if(!ff.extension().equals("zip")){
ff = ff.sibling(ff.nameWithoutExtension() + ".zip");
}
try{
data.exportData(ff);
ui.showInfo("$data.exported");
}catch(Exception e){
e.printStackTrace();
ui.showError(Strings.parseException(e, true));
}
}, false, f -> false);
}
});
t.row();
//iOS doesn't have a file chooser.
if(!ios){
t.addButton("$data.import", style, () -> ui.showConfirm("$confirm", "$data.import.confirm", () -> Platform.instance.showFileChooser("$data.import", "Zip Files", file -> {
try{
data.importData(file);
Core.app.exit();
}catch(IllegalArgumentException e){
ui.showError("$data.invalid");
}catch(Exception e){
e.printStackTrace();
ui.showError(Strings.parseException(e, true));
}
}, true, f -> f.equalsIgnoreCase("zip"))));
}
});
ScrollPane pane = new ScrollPane(prefs);
pane.addCaptureListener(new InputListener(){
@Override
@@ -121,6 +201,9 @@ public class SettingsMenuDialog extends SettingsDialog{
menu.row();
menu.addButton("$settings.controls", style, ui.controls::show);
}
menu.row();
menu.addButton("$settings.data", style, () -> dataDialog.show());
}
void addSettings(){
@@ -139,30 +222,7 @@ public class SettingsMenuDialog extends SettingsDialog{
game.checkPref("crashreport", true);
}
game.pref(new Setting(){
@Override
public void add(SettingsTable table){
table.addButton("$settings.cleardata", () -> ui.showConfirm("$confirm", "$settings.clearall.confirm", () -> {
ObjectMap<String, Object> map = new ObjectMap<>();
for(String value : Core.settings.keys()){
if(value.contains("usid") || value.contains("uuid")){
map.put(value, Core.settings.getString(value));
}
}
Core.settings.clear();
Core.settings.putAll(map);
Core.settings.save();
for(FileHandle file : dataDirectory.list()){
file.deleteDirectory();
}
Core.app.exit();
})).size(220f, 60f).pad(6).left();
table.add();
table.row();
}
});
game.checkPref("savecreate", true);
game.pref(new Setting(){
@Override
@@ -177,28 +237,13 @@ public class SettingsMenuDialog extends SettingsDialog{
}
});
if(android && (Core.files.local("mindustry-maps").exists() || Core.files.local("mindustry-saves").exists())){
game.pref(new Setting(){
@Override
public void add(SettingsTable table){
table.addButton("$classic.export", () -> {
control.checkClassicData();
}).size(220f, 60f).pad(6).left();
table.add();
table.row();
hide();
}
});
}
graphics.sliderPref("uiscale", 100, 25, 400, 25, s -> {
if(Core.graphics.getFrameId() > 10){
Log.info("changed");
if(ui.settings != null){
Core.settings.put("uiscalechanged", true);
}
return s + "%";
});
graphics.sliderPref("fpscap", 241, 5, 241, 5, s -> (s > 240 ? Core.bundle.get("setting.fpscap.none") : Core.bundle.format("setting.fpscap.text", s)));
graphics.sliderPref("fpscap", 240, 5, 245, 5, s -> (s > 240 ? Core.bundle.get("setting.fpscap.none") : Core.bundle.format("setting.fpscap.text", s)));
graphics.sliderPref("chatopacity", 100, 0, 100, 5, s -> s + "%");
if(!mobile){
@@ -207,7 +252,7 @@ public class SettingsMenuDialog extends SettingsDialog{
if(b){
Core.graphics.setFullscreenMode(Core.graphics.getDisplayMode());
}else{
Core.graphics.setWindowedMode(600, 480);
Core.graphics.setWindowedMode(Core.graphics.getWidth(), Core.graphics.getHeight());
}
});
@@ -241,7 +286,9 @@ public class SettingsMenuDialog extends SettingsDialog{
graphics.checkPref("fps", false);
graphics.checkPref("indicators", true);
graphics.checkPref("animatedwater", false);
graphics.checkPref("animatedshields", !mobile);
if(Shaders.shield != null){
graphics.checkPref("animatedshields", !mobile);
}
graphics.checkPref("bloom", false, val -> renderer.toggleBloom(val));
graphics.checkPref("lasers", true);
graphics.checkPref("pixelate", false);

View File

@@ -257,7 +257,7 @@ public class HudFragment extends Fragment{
t.table("flat", c -> c.add("$nearpoint")
.update(l -> l.setColor(Tmp.c1.set(Color.WHITE).lerp(Color.SCARLET, Mathf.absin(Time.time(), 10f, 1f))))
.get().setAlignment(Align.center, Align.center))
.margin(6).update(u -> u.color.a = Mathf.lerpDelta(u.color.a, Mathf.num(world.spawner.playerNear()), 0.1f)).get().color.a = 0f;
.margin(6).update(u -> u.color.a = Mathf.lerpDelta(u.color.a, Mathf.num(spawner.playerNear()), 0.1f)).get().color.a = 0f;
});
parent.fill(t -> {
@@ -543,7 +543,7 @@ public class HudFragment extends Fragment{
return world.isZone() &&
world.getZone().metCondition() &&
!Net.client() &&
state.wave % world.getZone().launchPeriod == 0 && !world.spawner.isSpawning();
state.wave % world.getZone().launchPeriod == 0 && !spawner.isSpawning();
}
private boolean canLaunch(){
@@ -639,7 +639,7 @@ public class HudFragment extends Fragment{
}
private boolean canSkipWave(){
return state.rules.waves && ((Net.server() || player.isAdmin) || !Net.active()) && state.enemies() == 0 && !world.spawner.isSpawning() && !state.rules.tutorial;
return state.rules.waves && ((Net.server() || player.isAdmin) || !Net.active()) && state.enemies() == 0 && !spawner.isSpawning() && !state.rules.tutorial;
}
private void addPlayButton(Table table){

View File

@@ -19,18 +19,19 @@ import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.graphics.MenuRenderer;
import io.anuke.mindustry.ui.MobileButton;
import static io.anuke.arc.Core.assets;
import static io.anuke.mindustry.Vars.*;
public class MenuFragment extends Fragment{
private Texture logo = new Texture("sprites/logo.png");
private Table container, submenu;
private Button currentMenu;
private MenuRenderer renderer;
public MenuFragment(){
assets.load("sprites/logo.png", Texture.class);
assets.finishLoading();
Events.on(DisposeEvent.class, event -> {
renderer.dispose();
logo.dispose();
});
}
@@ -59,19 +60,16 @@ public class MenuFragment extends Fragment{
}
});
//discord icon in top right
//parent.fill(c -> c.top().right().addButton("", "discord", ui.discord::show).size(84, 45)
//.visible(() -> state.is(State.menu)));
//info icon
if(mobile){
parent.fill(c -> c.bottom().left().addButton("", "info", ui.about::show).size(84, 45));
parent.fill(c -> c.bottom().right().addButton("", "discord", ui.discord::show).size(84, 45));
}
String versionText = "[#ffffffba]" + ((Version.build == -1) ? "[#fc8140aa]custom build" : Version.modifier + " build " + Version.build);
String versionText = "[#ffffffba]" + ((Version.build == -1) ? "[#fc8140aa]custom build" : (Version.type.equals("official") ? Version.modifier : Version.type) + " build " + Version.build);
parent.fill((x, y, w, h) -> {
Texture logo = Core.assets.get("sprites/logo.png");
float logoscl = UnitScl.dp.scl(1);
float logow = Math.min(logo.getWidth() * logoscl, Core.graphics.getWidth() - UnitScl.dp.scl(20));
float logoh = logow * (float)logo.getHeight() / logo.getWidth();
@@ -81,6 +79,7 @@ public class MenuFragment extends Fragment{
Draw.color();
Draw.rect(Draw.wrap(logo), fx, fy, logow, logoh);
Core.scene.skin.font().setColor(Color.WHITE);
Core.scene.skin.font().draw(versionText, fx, fy - logoh/2f, Align.center);
}).touchable(Touchable.disabled);

View File

@@ -1,26 +1,22 @@
package io.anuke.mindustry.ui.fragments;
import io.anuke.arc.Core;
import io.anuke.arc.Events;
import io.anuke.arc.collection.Array;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.input.KeyCode;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.scene.Group;
import io.anuke.arc.scene.event.Touchable;
import io.anuke.arc.scene.style.TextureRegionDrawable;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.input.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.input.Binding;
import io.anuke.mindustry.input.InputHandler;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Block.Icon;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.Block.*;
import static io.anuke.mindustry.Vars.*;
@@ -151,8 +147,7 @@ public class PlacementFragment extends Fragment{
button.update(() -> { //color unplacable things gray
TileEntity core = player.getClosestCore();
Color color = block.buildVisibility == Blocks.padVisible && !block.buildVisibility.get() ? Pal.noplace :
state.rules.infiniteResources || (core != null && (core.items.has(block.buildRequirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.WHITE : Color.GRAY;
Color color = state.rules.infiniteResources || (core != null && (core.items.has(block.buildRequirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.WHITE : Color.GRAY;
button.forEach(elem -> elem.setColor(color));
button.setChecked(input.block == block);
});
@@ -204,10 +199,6 @@ public class PlacementFragment extends Fragment{
Events.fire(new BlockInfoEvent());
}).size(8 * 5).padTop(-5).padRight(-5).right().grow().name("blockinfo");
}
if(lastDisplay.buildVisibility == Blocks.padVisible && !lastDisplay.buildVisibility.get()){
header.row();
header.add("$attackpvponly").width(230f).wrap().colspan(3).left();
}
}).growX().left();
topTable.row();
//add requirement table
@@ -305,7 +296,7 @@ public class PlacementFragment extends Fragment{
Array<Block> getByCategory(Category cat){
returnArray.clear();
for(Block block : content.blocks()){
if(block.buildCategory == cat && (block.isVisible() || block.buildVisibility == Blocks.padVisible)){
if(block.buildCategory == cat && block.isVisible()){
returnArray.add(block);
}
}

View File

@@ -104,6 +104,10 @@ public class Tile implements Position, TargetTrait{
return block().offset() + worldy();
}
public boolean isDarkened(){
return block().solid && !block().synthetic() && block().fillsTile;
}
public Floor floor(){
return floor;
}

View File

@@ -10,6 +10,7 @@ import static io.anuke.mindustry.Vars.*;
public class StaticWall extends Rock{
TextureRegion large;
TextureRegion[][] split;
public StaticWall(String name){
super(name);
@@ -25,9 +26,7 @@ public class StaticWall extends Rock{
int ry = tile.y / 2 * 2;
if(Core.atlas.isFound(large) && eq(rx, ry) && Mathf.randomSeed(Pos.get(rx, ry)) < 0.5){
if(rx == tile.x && ry == tile.y){
Draw.rect(large, tile.worldx() + tilesize / 2f, tile.worldy() + tilesize / 2f);
}
Draw.rect(split[tile.x % 2][1 - tile.y % 2], tile.worldx(), tile.worldy());
}else if(variants > 0){
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
}else{
@@ -39,6 +38,7 @@ public class StaticWall extends Rock{
public void load(){
super.load();
large = Core.atlas.find(name + "-large");
split = large.split(32, 32);
}
boolean eq(int rx, int ry){

View File

@@ -15,11 +15,12 @@ import io.anuke.mindustry.world.*;
import java.io.*;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public class Door extends Wall{
protected final Rectangle rect = new Rectangle();
protected int timerToggle = timers++;
protected Effect openfx = Fx.dooropen;
protected Effect closefx = Fx.doorclose;
@@ -39,7 +40,7 @@ public class Door extends Wall{
entity.open = open;
Door door = (Door)tile.block();
world.pathfinder.updateSolid(tile);
pathfinder.updateSolid(tile);
if(!entity.open){
Effects.effect(door.openfx, tile.drawx(), tile.drawy());
}else{
@@ -81,7 +82,7 @@ public class Door extends Wall{
public void tapped(Tile tile, Player player){
DoorEntity entity = tile.entity();
if(Units.anyEntities(tile) && entity.open){
if((Units.anyEntities(tile) && entity.open) || !tile.entity.timer.get(timerToggle, 30f)){
return;
}

View File

@@ -1,23 +1,20 @@
package io.anuke.mindustry.world.blocks.defense;
import io.anuke.arc.Core;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.graphics.Blending;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.content.Fx;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.impl.BaseEntity;
import io.anuke.mindustry.entities.impl.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.consumers.*;
import io.anuke.mindustry.world.meta.BlockStat;
import io.anuke.mindustry.world.meta.StatUnit;
import io.anuke.mindustry.world.meta.*;
import java.io.*;
@@ -94,7 +91,6 @@ public class ForceProjector extends Block{
@Override
public void update(Tile tile){
ForceEntity entity = tile.entity();
boolean cheat = tile.isEnemyCheat();
if(entity.shield == null){
entity.shield = new ShieldEntity(tile);
@@ -109,21 +105,15 @@ public class ForceProjector extends Block{
entity.cons.trigger();
}
entity.radscl = Mathf.lerpDelta(entity.radscl, entity.broken ? 0f : 1f, 0.05f);
entity.radscl = Mathf.lerpDelta(entity.radscl, entity.broken ? 0f : entity.warmup, 0.05f);
if(Mathf.chance(Time.delta() * entity.buildup / breakage * 0.1f)){
Effects.effect(Fx.reactorsmoke, tile.drawx() + Mathf.range(tilesize / 2f), tile.drawy() + Mathf.range(tilesize / 2f));
}
//use cases:
// - There is enough power in the buffer, and there are no shots fired => Draw base power and keep shield up
// - There is enough power in the buffer, but not enough power to cope for shots being fired => Draw all power and break shield
// - There is enough power in the buffer and enough power to cope for shots being fired => Draw base power + additional power based on shots absorbed
// - There is not enough base power in the buffer => Draw all power and break shield
// - The generator is in the AI base and uses cheat mode => Only draw power from shots being absorbed
float relativePowerDraw = cheat ? 0f : 1f;
entity.warmup = Mathf.lerpDelta(entity.warmup, entity.power.satisfaction, 0.1f);
/*
if(entity.power.satisfaction < relativePowerDraw){
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.15f);
entity.power.satisfaction = 0f;
@@ -132,7 +122,7 @@ public class ForceProjector extends Block{
}
}else{
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.1f);
}
}*/
if(entity.buildup > 0){
float scale = !entity.broken ? cooldownNormal : cooldownBrokenBase;
@@ -145,7 +135,7 @@ public class ForceProjector extends Block{
entity.buildup -= Time.delta() * scale;
}
if(entity.broken && entity.buildup <= 0 && entity.warmup >= 0.9f){
if(entity.broken && entity.buildup <= 0){
entity.broken = false;
}

View File

@@ -101,7 +101,7 @@ public class MendProjector extends Block{
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Drawf.dashCircle(x * tilesize, y * tilesize, range, Pal.accent);
Drawf.dashCircle(x * tilesize + offset(), y * tilesize + offset(), range, Pal.accent);
}
@Override

View File

@@ -109,10 +109,7 @@ public class PowerGraph{
for(Tile battery : batteries){
Consumers consumes = battery.block().consumes;
if(consumes.hasPower()){
ConsumePower consumePower = consumes.getPower();
if(consumePower.capacity > 0f){
battery.entity.power.satisfaction = Math.max(0.0f, battery.entity.power.satisfaction - consumedPowerPercentage);
}
battery.entity.power.satisfaction *= (1f-consumedPowerPercentage);
}
}
return used;

View File

@@ -122,7 +122,7 @@ public class PowerNode extends PowerBlock{
private void getPotentialLinks(Tile tile, Consumer<Tile> others){
Predicate<Tile> valid = other -> other != null && other != tile && other.entity != null && other.entity.power != null &&
((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower) || other.block() instanceof PowerNode) &&
overlaps(tile.x * tilesize + offset(), tile.y *tilesize + offset(), other, laserRange * tilesize)
overlaps(tile.x * tilesize + offset(), tile.y *tilesize + offset(), other, laserRange * tilesize) && other.getTeam() == player.getTeam()
&& !other.entity.proximity().contains(tile) && !graphs.contains(other.entity.power.graph);
tempTiles.clear();
@@ -222,20 +222,7 @@ public class PowerNode extends PowerBlock{
Draw.color(Pal.placing);
Drawf.circles(x * tilesize + offset(), y * tilesize + offset(), laserRange * tilesize);
getPotentialLinks(tile, other -> {
Drawf.square(other.drawx(), other.drawy(), other.block().size * tilesize / 2f + 2f, Pal.place);
});
/*
for(int cx = (int)(x - laserRange - 1); cx <= x + laserRange + 1; cx++){
for(int cy = (int)(y - laserRange - 1); cy <= y + laserRange + 1; cy++){
Tile link = world.ltile(cx, cy);
if(link != null && !(link.x == x && link.y == y) && link.block().hasPower && overlaps(x * tilesize + offset(), y *tilesize + offset(), link, laserRange * tilesize)){
Drawf.square(link.drawx(), link.drawy(), link.block().size * tilesize / 2f + 2f, link.pos() == lastPlaced ? Pal.place : Pal.accent);
}
}
}*/
getPotentialLinks(tile, other -> Drawf.square(other.drawx(), other.drawy(), other.block().size * tilesize / 2f + 2f, Pal.place));
Draw.reset();
}

View File

@@ -0,0 +1,129 @@
package io.anuke.mindustry.world.blocks.units;
import io.anuke.annotations.Annotations.*;
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.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.meta.*;
import java.io.*;
import static io.anuke.mindustry.Vars.*;
public class CommandCenter extends Block{
protected TextureRegion[] commandRegions = new TextureRegion[UnitCommand.all.length];
protected Color topColor = Pal.command;
protected Color bottomColor = Color.valueOf("5e5e5e");
protected Effect effect = Fx.commandSend;
public CommandCenter(String name){
super(name);
flags = EnumSet.of(BlockFlag.comandCenter);
destructible = true;
solid = true;
configurable = true;
}
@Override
public void placed(Tile tile){
super.placed(tile);
ObjectSet<Tile> set = indexer.getAllied(tile.getTeam(), BlockFlag.comandCenter);
if(set.size > 0){
CommandCenterEntity entity = tile.entity();
CommandCenterEntity oe = set.first().entity();
entity.command = oe.command;
}
}
@Override
public void load(){
super.load();
for(UnitCommand cmd : UnitCommand.all){
commandRegions[cmd.ordinal()] = Core.atlas.find("icon-command-" + cmd.name() + "-small");
}
}
@Override
public void draw(Tile tile){
CommandCenterEntity entity = tile.entity();
super.draw(tile);
float size = iconsizesmall/4f;
Draw.color(bottomColor);
Draw.rect(commandRegions[entity.command.ordinal()], tile.drawx(), tile.drawy() - 1, size, size);
Draw.color(topColor);
Draw.rect(commandRegions[entity.command.ordinal()], tile.drawx(), tile.drawy(), size, size);
Draw.color();
}
@Override
public void buildTable(Tile tile, Table table){
CommandCenterEntity entity = tile.entity();
ButtonGroup<ImageButton> group = new ButtonGroup<>();
Table buttons = new Table();
for(UnitCommand cmd : UnitCommand.all){
buttons.addImageButton("icon-command-" + cmd.name() + "-small", "clear-toggle-trans", iconsizesmall, () -> Call.onCommandCenterSet(player, tile, cmd))
.size(44).group(group).update(b -> b.setChecked(entity.command == cmd));
}
table.add(buttons);
table.row();
table.label(() -> entity.command.localized()).style("outline").center().growX().get().setAlignment(Align.center);
}
@Remote(called = Loc.server, forward = true, targets = Loc.both)
public static void onCommandCenterSet(Player player, Tile tile, UnitCommand command){
Effects.effect(((CommandCenter)tile.block()).effect, tile);
for(Tile center : indexer.getAllied(tile.getTeam(), BlockFlag.comandCenter)){
if(center.block() instanceof CommandCenter){
CommandCenterEntity entity = center.entity();
entity.command = command;
}
}
Team team = (player == null ? tile.getTeam() : player.getTeam());
for(BaseUnit unit : unitGroups[team.ordinal()].all()){
unit.onCommand(command);
}
}
@Override
public TileEntity newEntity(){
return new CommandCenterEntity();
}
public class CommandCenterEntity extends TileEntity{
public UnitCommand command = UnitCommand.attack;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeByte(command.ordinal());
}
@Override
public void read(DataInput stream, byte version) throws IOException{
super.read(stream, version);
command = UnitCommand.all[stream.readByte()];
}
}
}

View File

@@ -7,6 +7,8 @@ public enum BlockFlag{
producer(Float.MAX_VALUE),
/** A turret. */
turret(Float.MAX_VALUE),
/** Only the command center block.*/
comandCenter(Float.MAX_VALUE),
/** Repair point. */
repair(Float.MAX_VALUE);