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

# Conflicts:
#	core/src/io/anuke/mindustry/entities/type/FlyingUnit.java
#	gradle.properties
This commit is contained in:
Anuken
2019-09-29 17:03:28 -04:00
58 changed files with 1012 additions and 273 deletions

View File

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

View File

@@ -18,14 +18,14 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import java.nio.charset.*;
import java.util.*;
import static io.anuke.arc.Core.settings;
import static io.anuke.arc.Core.*;
@SuppressWarnings("unchecked")
public class Vars implements Loadable{
@@ -120,8 +120,8 @@ public class Vars implements Loadable{
public static FileHandle tmpDirectory;
/** data subdirectory used for saves */
public static FileHandle saveDirectory;
/** data subdirectory used for plugins */
public static FileHandle pluginDirectory;
/** data subdirectory used for mods */
public static FileHandle modDirectory;
/** map file extension */
public static final String mapExtension = "msav";
/** save file extension */
@@ -130,6 +130,7 @@ public class Vars implements Loadable{
/** list of all locales that can be switched to */
public static Locale[] locales;
public static FileTree filet;
public static Net net;
public static ContentLoader content;
public static GameState state;
@@ -138,7 +139,7 @@ public class Vars implements Loadable{
public static DefaultWaves defaultWaves;
public static LoopControl loops;
public static Platform platform = new Platform(){};
public static Plugins plugins;
public static Mods mods;
public static World world;
public static Maps maps;
@@ -193,6 +194,10 @@ public class Vars implements Loadable{
Version.init();
filet = new FileTree();
if(mods == null){
mods = new Mods();
}
content = new ContentLoader();
loops = new LoopControl();
defaultWaves = new DefaultWaves();
@@ -240,8 +245,11 @@ public class Vars implements Loadable{
mapPreviewDirectory = dataDirectory.child("previews/");
saveDirectory = dataDirectory.child("saves/");
tmpDirectory = dataDirectory.child("tmp/");
pluginDirectory = dataDirectory.child("plugins/");
modDirectory = dataDirectory.child("mods/");
modDirectory.mkdirs();
mods.load();
maps.load();
}

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ public class UnitTypes implements ContentList{
@Override
public void load(){
draug = new UnitType("draug", Draug.class, Draug::new){{
isFlying = true;
flying = true;
drag = 0.01f;
speed = 0.3f;
maxVelocity = 1.2f;
@@ -33,7 +33,7 @@ public class UnitTypes implements ContentList{
}};
spirit = new UnitType("spirit", Spirit.class, Spirit::new){{
isFlying = true;
flying = true;
drag = 0.01f;
speed = 0.4f;
maxVelocity = 1.6f;
@@ -54,7 +54,7 @@ public class UnitTypes implements ContentList{
}};
phantom = new UnitType("phantom", Phantom.class, Phantom::new){{
isFlying = true;
flying = true;
drag = 0.01f;
mass = 2f;
speed = 0.45f;
@@ -244,7 +244,7 @@ public class UnitTypes implements ContentList{
maxVelocity = 1.9f;
drag = 0.01f;
mass = 1.5f;
isFlying = true;
flying = true;
health = 75;
engineOffset = 5.5f;
range = 140f;
@@ -264,7 +264,7 @@ public class UnitTypes implements ContentList{
maxVelocity = 1.4f;
mass = 3f;
drag = 0.01f;
isFlying = true;
flying = true;
targetAir = false;
engineOffset = 7.8f;
range = 140f;
@@ -291,7 +291,7 @@ public class UnitTypes implements ContentList{
drag = 0.01f;
range = 80f;
shootCone = 40f;
isFlying = true;
flying = true;
rotateWeapon = true;
engineOffset = 12f;
engineSize = 3f;
@@ -322,7 +322,7 @@ public class UnitTypes implements ContentList{
drag = 0.02f;
range = 80f;
shootCone = 20f;
isFlying = true;
flying = true;
rotateWeapon = true;
engineOffset = 21;
engineSize = 5.3f;
@@ -355,7 +355,7 @@ public class UnitTypes implements ContentList{
drag = 0.02f;
range = 80f;
shootCone = 30f;
isFlying = true;
flying = true;
rotateWeapon = true;
engineOffset = 40;
engineSize = 7.3f;

View File

@@ -11,6 +11,7 @@ import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import static io.anuke.arc.Core.files;
import static io.anuke.mindustry.Vars.mods;
/**
* Loads all game content.
@@ -57,6 +58,23 @@ public class ContentLoader{
list.load();
}
setupMapping();
if(mods != null){
mods.loadContent();
}
setupMapping();
loaded = true;
}
private void setupMapping(){
for(ContentType type : ContentType.values()){
contentNameMap[type.ordinal()].clear();
}
for(ContentType type : ContentType.values()){
for(Content c : contentMap[type.ordinal()]){
@@ -79,8 +97,6 @@ public class ContentLoader{
}
}
}
loaded = true;
}
/** Logs content statistics.*/

View File

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

View File

@@ -200,6 +200,11 @@ public class NetServer implements ApplicationListener{
registerCommands();
}
@Override
public void init(){
mods.each(mod -> mod.registerClientCommands(clientCommands));
}
private void registerCommands(){
clientCommands.<Player>register("help", "[page]", "Lists all commands.", (args, player) -> {
if(args.length > 0 && !Strings.canParseInt(args[0])){

View File

@@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
public DeployDialog deploy;
public TechTreeDialog tech;
public MinimapDialog minimap;
public ModsDialog mods;
public Cursor drillCursor, unloadCursor;
@@ -222,6 +223,7 @@ public class UI implements ApplicationListener, Loadable{
deploy = new DeployDialog();
tech = new TechTreeDialog();
minimap = new MinimapDialog();
mods = new ModsDialog();
Group group = Core.scene.root;
@@ -410,6 +412,18 @@ public class UI implements ApplicationListener, Loadable{
dialog.show();
}
public void showOkText(String title, String text, Runnable confirmed){
FloatingDialog dialog = new FloatingDialog(title);
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.addButton("$ok", () -> {
dialog.hide();
confirmed.run();
});
dialog.show();
}
public String formatAmount(int number){
if(number >= 1000000){
return Strings.fixed(number / 1000000f, 1) + "[gray]mil[]";

View File

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

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.editor;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
@@ -16,6 +15,7 @@ import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
@@ -24,7 +24,7 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.ui.Styles;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;

View File

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

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.Queue;
import io.anuke.arc.collection.*;
@@ -8,6 +7,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.*;
@@ -200,7 +200,8 @@ public interface BuilderTrait extends Entity, TeamTrait{
* Return the build requests currently active, or the one at the top of the queue.
* May return null.
*/
default @Nullable BuildRequest buildRequest(){
default @Nullable
BuildRequest buildRequest(){
return buildQueue().size == 0 ? null : buildQueue().first();
}

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
@@ -93,7 +94,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
}
public @Nullable Tile getSpawner(){
public @Nullable
Tile getSpawner(){
return world.tile(spawner);
}
@@ -263,7 +265,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
@Override
public boolean isFlying(){
return type.isFlying;
return type.flying;
}
@Override

View File

@@ -10,6 +10,7 @@ import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
@@ -48,7 +49,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public float baseRotation;
public float pointerX, pointerY;
public String name = "noname";
public @Nullable String uuid, usid;
public @Nullable
String uuid, usid;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping;
public float boostHeat, shootHeat, destructTime;
public boolean achievedFlight;

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.collection.ObjectSet;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.HealthTrait;
import io.anuke.mindustry.entities.traits.TargetTrait;
@@ -42,7 +43,8 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
private boolean dead = false;
private boolean sleeping;
private float sleepTime;
private @Nullable SoundLoop sound;
private @Nullable
SoundLoop sound;
@Remote(called = Loc.server, unreliable = true)
public static void onTileDamage(Tile tile, float health){

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
@@ -9,6 +8,7 @@ import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
@@ -234,7 +234,8 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
}
public @Nullable TileEntity getClosestCore(){
public @Nullable
TileEntity getClosestCore(){
TeamData data = state.teams.get(team);
Tile tile = Geometry.findClosest(x, y, data.cores);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.type;
package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
@@ -8,6 +8,7 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
@@ -15,7 +16,7 @@ import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
public abstract class FlyingUnit extends BaseUnit{
public class FlyingUnit extends BaseUnit{
protected float[] weaponAngles = {0,0};
protected final UnitState

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.audio.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
@@ -22,7 +22,8 @@ public class MusicControl{
public Array<Music> darkMusic = Array.with();
private Music lastRandomPlayed;
private Interval timer = new Interval();
private @Nullable Music current;
private @Nullable
Music current;
private float fade;
private boolean silenced;

View File

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

View File

@@ -0,0 +1,193 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.reflect.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Json.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@SuppressWarnings("unchecked")
public class ContentParser{
private static final boolean ignoreUnknownFields = true;
private ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
private Json parser = new Json(){
public <T> T readValue(Class<T> type, Class elementType, JsonValue jsonData){
try{
if(type == BulletType.class){
BulletType b = (BulletType)Bullets.class.getField(jsonData.asString()).get(null);
if(b == null) throw new IllegalArgumentException("Bullet type not found: " + jsonData.asString());
return (T)b;
}
if(type != null && Content.class.isAssignableFrom(type)){
return (T)Vars.content.getByName(contentTypes.getThrow(type, () -> new IllegalArgumentException("No content type for class: " + type.getSimpleName())), jsonData.asString());
}
return super.readValue(type, elementType, jsonData);
}catch(Exception e){
throw new RuntimeException(e);
}
}
};
private ObjectMap<ContentType, TypeParser<?>> parsers = ObjectMap.of(
ContentType.block, (TypeParser<Block>)(mod, name, value) -> {
String clas = value.getString("type");
Class<Block> type = resolve("io.anuke.mindustry.world." + clas, "io.anuke.mindustry.world.blocks." + clas, "io.anuke.mindustry.world.blocks.defense" + clas);
Block block = type.getDeclaredConstructor(String.class).newInstance(mod + "-" + name);
value.remove("type");
readFields(block, value);
//make block visible
if(block.buildRequirements != null){
block.buildVisibility = () -> true;
}
return block;
},
ContentType.item, (TypeParser<Item>)(mod, name, value) -> {
Item item = new Item(mod + "-" + name, new Color(Color.black));
readFields(item, value);
return item;
},
ContentType.unit, (TypeParser<UnitType>)(mod, name, value) -> {
String clas = value.getString("type");
Class<BaseUnit> type = resolve("io.anuke.mindustry.entities.type.base." + clas);
java.lang.reflect.Constructor<BaseUnit> cons = type.getDeclaredConstructor();
UnitType unit = new UnitType(mod + "-" + name, type, () -> {
try{
return cons.newInstance();
}catch(Exception e){
throw new RuntimeException(e);
}
});
value.remove("type");
readFields(unit, value);
return unit;
}
);
private void init(){
for(ContentType type : ContentType.all){
Array<Content> arr = Vars.content.getBy(type);
if(!arr.isEmpty()){
Class<?> c = arr.first().getClass();
//get base content class, skipping intermediates
while(!(c.getSuperclass() == Content.class || c.getSuperclass() == UnlockableContent.class || c.getSuperclass() == UnlockableContent.class)){
c = c.getSuperclass();
}
contentTypes.put(c, type);
}
}
}
/**
* Parses content from a json file.
* @param name the name of the file without its extension
* @param json the json to parse
* @param type the type of content this is
* @return the content that was parsed
*/
public Content parse(String mod, String name, String json, ContentType type) throws Exception{
if(contentTypes.isEmpty()){
init();
}
JsonValue value = parser.fromJson(null, json);
if(!parsers.containsKey(type)){
throw new SerializationException("No parsers for content type '" + type + "'");
}
Content c = parsers.get(type).parse(mod, name, value);
checkNulls(c);
return c;
}
/** Checks all @NonNull fields in this object, recursively.
* Throws an exception if any are null.*/
private void checkNulls(Object object){
checkNulls(object, new ObjectSet<>());
}
private void checkNulls(Object object, ObjectSet<Object> checked){
checked.add(object);
parser.getFields(object.getClass()).each((name, field) -> {
try{
if(field.field.getType().isPrimitive()) return;
Object obj = field.field.get(object);
if(field.field.isAnnotationPresent(NonNull.class) && field.field.get(object) == null){
throw new RuntimeException("Field '" + name + "' in " + object.getClass().getSimpleName() + " is missing!");
}
if(obj != null && !checked.contains(obj)){
checkNulls(obj, checked);
checked.add(obj);
}
}catch(Exception e){
throw new RuntimeException(e);
}
});
}
private void readFields(Object object, JsonValue jsonMap){
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = parser.getFields(type);
for(JsonValue child = jsonMap.child; child != null; child = child.next){
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
if(metadata == null){
if(ignoreUnknownFields){
Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object);
continue;
}else{
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
ex.addTrace(child.trace());
throw ex;
}
}
Field field = metadata.field;
try{
field.set(object, parser.readValue(field.getType(), metadata.elementType, child));
}catch(ReflectionException ex){
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex);
}catch(SerializationException ex){
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}catch(RuntimeException runtimeEx){
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(child.trace());
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}
}
}
/** Tries to resolve a class from a list of potential class names. */
private <T> Class<T> resolve(String... potentials) throws Exception{
for(String type : potentials){
try{
return (Class<T>)Class.forName(type);
}catch(Exception ignored){
}
}
throw new IllegalArgumentException("Type not found: " + potentials[0]);
}
public interface TypeParser<T extends Content>{
T parse(String mod, String name, JsonValue value) throws Exception;
}
}

View File

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

View File

@@ -0,0 +1,284 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Pixmap.*;
import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import java.io.*;
import java.net.*;
import static io.anuke.mindustry.Vars.*;
public class Mods implements Loadable{
private Json json = new Json();
private ContentParser parser = new ContentParser();
private ObjectMap<String, Array<FileHandle>> bundles = new ObjectMap<>();
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites");
private int totalSprites;
private PixmapPacker packer;
private Array<LoadedMod> loaded = new Array<>();
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
private boolean requiresRestart;
/** Returns a file named 'config.json' in a special folder for the specified plugin.
* Call this in init(). */
public FileHandle getConfig(Mod mod){
ModMeta load = metas.get(mod.getClass());
if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!");
return modDirectory.child(load.name).child("config.json");
}
/** @return the loaded mod found by class, or null if not found. */
public @Nullable
LoadedMod getMod(Class<? extends Mod> type){
return loaded.find(l -> l.mod.getClass() == type);
}
/** Imports an external mod file.*/
public void importMod(FileHandle file) throws IOException{
FileHandle dest = modDirectory.child(file.name());
if(dest.exists()){
throw new IOException("A mod with the same filename already exists!");
}
file.copyTo(dest);
try{
loaded.add(loadMod(file));
requiresRestart = true;
}catch(IOException e){
dest.delete();
throw e;
}catch(Throwable t){
dest.delete();
throw new IOException(t);
}
}
/** Repacks all in-game sprites. */
@Override
public void loadAsync(){
if(loaded.isEmpty()) return;
packer = new PixmapPacker(2048, 2048, Format.RGBA8888, 2, true);
for(LoadedMod mod : loaded){
try{
int packed = 0;
for(FileHandle file : mod.root.child("sprites").list()){
if(file.extension().equals("png")){
try(InputStream stream = file.read()){
byte[] bytes = Streams.copyStreamToByteArray(stream, Math.max((int)file.length(), 512));
Pixmap pixmap = new Pixmap(bytes, 0, bytes.length);
packer.pack(mod.name + "-" + file.nameWithoutExtension(), pixmap);
pixmap.dispose();
packed ++;
totalSprites ++;
}
}
}
Log.info("Packed {0} images for mod '{1}'.", packed, mod.meta.name);
}catch(IOException e){
Log.err("Error packing images for mod: {0}", mod.meta.name);
e.printStackTrace();
if(!headless) ui.showException(e);
}
}
}
@Override
public void loadSync(){
if(packer == null) return;
if(totalSprites > 0){
TextureFilter filter = Core.settings.getBool("linear") ? TextureFilter.Linear : TextureFilter.Nearest;
packer.getPages().each(page -> page.updateTexture(filter, filter, false));
packer.getPages().each(page -> page.getRects().each((name, rect) -> Core.atlas.addRegion(name, page.getTexture(), (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height)));
}
packer.dispose();
}
/** Removes a mod file and marks it for requiring a restart. */
public void removeMod(LoadedMod mod){
if(mod.file.isDirectory()){
mod.file.deleteDirectory();
}else{
mod.file.delete();
}
loaded.remove(mod);
requiresRestart = true;
}
public boolean requiresRestart(){
return requiresRestart;
}
/** Loads all mods from the folder, but does call any methods on them.*/
public void load(){
for(FileHandle file : modDirectory.list()){
if(!file.extension().equals("jar") && !file.extension().equals("zip") && !(file.isDirectory() && file.child("mod.json").exists())) continue;
try{
loaded.add(loadMod(file));
}catch(IllegalArgumentException ignored){
}catch(Exception e){
Log.err("Failed to load plugin file {0}. Skipping.", file);
e.printStackTrace();
}
}
buildFiles();
}
private void buildFiles(){
for(LoadedMod mod : loaded){
for(FileHandle file : mod.root.list()){
//ignore special folders like bundles or sprites
if(file.isDirectory() && !specialFolders.contains(file.name())){
//TODO calling child/parent on these files will give you gibberish; create wrapper class.
file.walk(f -> filet.addFile(f));
}
}
//load up bundles.
FileHandle folder = mod.root.child("bundles");
if(folder.exists()){
for(FileHandle file : folder.list()){
if(file.name().startsWith("bundle") && file.extension().equals("properties")){
String name = file.nameWithoutExtension();
bundles.getOr(name, Array::new).add(file);
}
}
}
}
//add new keys to each bundle
I18NBundle bundle = Core.bundle;
while(bundle != null){
String str = bundle.getLocale().toString();
String locale = "bundle" + (str.isEmpty() ? "" : "_" + str);
for(FileHandle file : bundles.getOr(locale, Array::new)){
try{
PropertiesUtils.load(bundle.getProperties(), file.reader());
}catch(Exception e){
throw new RuntimeException("Error loading bundle: " + file + "/" + locale, e);
}
}
bundle = bundle.getParent();
}
}
/** Creates all the content found in mod files. */
public void loadContent(){
for(LoadedMod mod : loaded){
if(mod.root.child("content").exists()){
FileHandle contentRoot = mod.root.child("content");
for(ContentType type : ContentType.all){
FileHandle folder = contentRoot.child(type.name().toLowerCase() + "s");
if(folder.exists()){
for(FileHandle file : folder.list()){
if(file.extension().equals("json")){
try{
Content loaded = parser.parse(mod.name, file.nameWithoutExtension(), file.readString(), type);
Log.info("[{0}] Loaded '{1}'.", mod.meta.name, loaded);
}catch(Exception e){
throw new RuntimeException("Failed to parse content file '" + file + "' for mod '" + mod.meta.name + "'.", e);
}
}
}
}
}
}
}
each(Mod::loadContent);
}
/** @return all loaded mods. */
public Array<LoadedMod> all(){
return loaded;
}
/** Iterates through each mod with a main class.*/
public void each(Consumer<Mod> cons){
loaded.each(p -> p.mod != null, p -> cons.accept(p.mod));
}
/** Loads a mod file+meta, but does not add it to the list.
* Note that directories can be loaded as mods.*/
private LoadedMod loadMod(FileHandle sourceFile) throws Exception{
FileHandle zip = sourceFile.isDirectory() ? sourceFile : new ZipFileHandle(sourceFile);
FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("plugin.json");
if(!metaf.exists()){
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", sourceFile);
throw new IllegalArgumentException("No mod.json found.");
}
ModMeta meta = json.fromJson(ModMeta.class, metaf.readString());
String camelized = meta.name.replace(" ", "");
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
Mod mainMod;
//make sure the main class exists before loading it; if it doesn't just don't put it there
if(zip.child(mainClass.replace('.', '/') + ".class").exists()){
//other platforms don't have standard java class loaders
if(mobile){
throw new IllegalArgumentException("This mod is not compatible with " + (ios ? "iOS" : "Android") + ".");
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{sourceFile.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(mainClass);
metas.put(main, meta);
mainMod = (Mod)main.getDeclaredConstructor().newInstance();
}else{
mainMod = null;
}
return new LoadedMod(sourceFile, zip, mainMod, meta);
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedMod{
/** The location of this mod's zip file/folder on the disk. */
public final FileHandle file;
/** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */
public final FileHandle root;
/** The mod's main class; may be null. */
public final @Nullable Mod mod;
/** Internal mod name. Used for textures. */
public final String name;
/** This mod's metadata. */
public final ModMeta meta;
//TODO implement
protected boolean enabled;
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){
this.root = root;
this.file = file;
this.mod = mod;
this.meta = meta;
this.name = meta.name.toLowerCase().replace(" ", "-");
}
}
/** Plugin metadata information.*/
public static class ModMeta{
public String name, author, description, version, main;
public String[] dependencies = {}; //TODO implement
}
}

View File

@@ -26,7 +26,7 @@ public class CrashSender{
exception.printStackTrace();
//don't create crash logs for custom builds, as it's expected
if(Version.build == -1) return;
if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return;
//attempt to load version regardless
if(Version.number == 0){
@@ -143,8 +143,7 @@ public class CrashSender{
private static void ex(Runnable r){
try{
r.run();
}catch(Throwable t){
t.printStackTrace();
}catch(Throwable ignored){
}
}
}

View File

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

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.net;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Administration.*;
@@ -15,7 +15,8 @@ import static io.anuke.mindustry.Vars.netServer;
public abstract class NetConnection{
public final String address;
public boolean mobile, modclient;
public @Nullable Player player;
public @Nullable
Player player;
/** ID of last recieved client snapshot. */
public int lastRecievedClientSnapshot = -1;

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
@@ -13,8 +14,9 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.ui.*;
public class UnitType extends UnlockableContent{
public final TypeID typeID;
public final Supplier<? extends BaseUnit> constructor;
public final @NonNull
TypeID typeID;
public final @NonNull Supplier<? extends BaseUnit> constructor;
public float health = 60;
public float hitsize = 7f;
@@ -25,7 +27,7 @@ public class UnitType extends UnlockableContent{
public float baseRotateSpeed = 0.1f;
public float shootCone = 15f;
public float mass = 1f;
public boolean isFlying;
public boolean flying;
public boolean targetAir = true;
public boolean rotateWeapon = false;
public float drag = 0.1f;
@@ -34,7 +36,7 @@ public class UnitType extends UnlockableContent{
public int itemCapacity = 30;
public ObjectSet<Item> toMine = ObjectSet.with(Items.lead, Items.copper);
public float buildPower = 0.3f, minePower = 0.7f;
public Weapon weapon;
public @NonNull Weapon weapon;
public float weaponOffsetY, engineOffset = 6f, engineSize = 2f;
public ObjectSet<StatusEffect> immunities = new ObjectSet<>();
public Sound deathSound = Sounds.bang;

View File

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

View File

@@ -1,10 +1,10 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.maps.*;
@@ -15,7 +15,8 @@ import static io.anuke.mindustry.Vars.*;
public class MapPlayDialog extends FloatingDialog{
CustomRulesDialog dialog = new CustomRulesDialog();
Rules rules;
@NonNull Gamemode selectedGamemode = Gamemode.survival;
@NonNull
Gamemode selectedGamemode = Gamemode.survival;
Map lastMap;
public MapPlayDialog(){

View File

@@ -0,0 +1,88 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.ui.*;
import java.io.*;
import static io.anuke.mindustry.Vars.*;
public class ModsDialog extends FloatingDialog{
public ModsDialog(){
super("$mods");
addCloseButton();
shown(this::setup);
hidden(() -> {
if(mods.requiresRestart()){
ui.showOkText("$mods", "$mod.requiresrestart", () -> {
Core.app.exit();
});
}
});
shown(() -> Core.app.post(() -> {
Core.settings.getBoolOnce("modsalpha", () -> {
ui.showText("$mods", "$mods.alphainfo");
});
}));
}
void setup(){
cont.clear();
cont.defaults().width(520f).pad(4);
if(!mods.all().isEmpty()){
cont.pane(table -> {
table.margin(10f).top();
for(LoadedMod mod : mods.all()){
table.table(Styles.black6, t -> {
t.defaults().pad(2).left().top();
t.margin(14f).left();
t.table(title -> {
title.left();
title.add("[accent]" + mod.meta.name + "[lightgray] v" + mod.meta.version);
title.add().growX();
title.addImageButton(Icon.trash16Small, Styles.cleari, () -> ui.showConfirm("$confirm", "$mod.remove.confirm", () -> {
mods.removeMod(mod);
setup();
})).size(50f);
}).growX().left().padTop(-14f).padRight(-14f);
t.row();
if(mod.meta.author != null){
t.add(Core.bundle.format("mod.author", mod.meta.author));
t.row();
}
if(mod.meta.description != null){
t.labelWrap("[lightgray]" + mod.meta.description).growX();
t.row();
}
}).width(500f);
table.row();
}
});
}else{
cont.table(Styles.black6, t -> t.add("$mods.none")).height(80f);
}
cont.row();
cont.addImageTextButton("$mod.import", Icon.add, () -> {
platform.showFileChooser(true, "zip", file -> {
try{
mods.importMod(file);
setup();
}catch(IOException e){
ui.showException(e);
e.printStackTrace();
}
});
}).margin(12f).width(500f);
}
}

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.*;

View File

@@ -163,6 +163,7 @@ public class MenuFragment extends Fragment{
),
new Buttoni("$editor", Icon.editorSmall, ui.maps::show),
steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null,
new Buttoni(Core.bundle.get("mods") + "\n" + Core.bundle.get("mods.alpha"), Icon.wikiSmall, ui.mods::show),
new Buttoni("$settings", Icon.toolsSmall, ui.settings::show),
new Buttoni("$about.button", Icon.infoSmall, ui.about::show),
new Buttoni("$quit", Icon.exitSmall, Core.app::exit)

View File

@@ -8,6 +8,7 @@ import io.anuke.arc.Graphics.Cursor.SystemCursor;
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.ArcAnnotate.*;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.effect.RubbleDecal;
@@ -172,7 +173,8 @@ public class BuildBlock extends Block{
* The recipe of the block that is being constructed.
* If there is no recipe for this block, as is the case with rocks, 'previous' is used.
*/
public @Nullable Block cblock;
public @Nullable
Block cblock;
public float progress = 0;
public float buildCost;

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.world.blocks.storage;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Block;
@@ -75,6 +75,7 @@ public abstract class StorageBlock extends Block{
}
public class StorageBlockEntity extends TileEntity{
protected @Nullable Tile linkedCore;
protected @Nullable
Tile linkedCore;
}
}