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

# Conflicts:
#	core/assets/sprites/block_colors.png
#	core/assets/sprites/sprites.atlas
#	core/assets/sprites/sprites.png
#	core/assets/sprites/sprites2.png
#	core/assets/sprites/sprites5.png
This commit is contained in:
Anuken
2019-09-21 18:59:49 -04:00
267 changed files with 11249 additions and 6885 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.net.Net;
import static io.anuke.arc.Core.*;
import static io.anuke.mindustry.Vars.*;
@@ -39,7 +40,9 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
batch = new SpriteBatch();
assets = new AssetManager();
assets.setLoader(Texture.class, "." + mapExtension, new MapPreviewLoader());
assets.load("sprites/error.png", Texture.class);
atlas = TextureAtlas.blankAtlas();
Vars.net = new Net(platform.getNet());
UI.loadSystemCursors();
@@ -47,7 +50,9 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
UI.loadDefaultFont();
assets.load(new AssetDescriptor<>("sprites/sprites.atlas", TextureAtlas.class)).loaded = t -> atlas = (TextureAtlas)t;
assets.load(new AssetDescriptor<>("sprites/sprites.atlas", TextureAtlas.class)).loaded = t -> {
atlas = (TextureAtlas)t;
};
assets.loadRun("maps", Map.class, () -> maps.loadPreviews());
@@ -84,6 +89,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void resize(int width, int height){
if(assets == null) return;
if(!assets.isFinished()){
Draw.proj().setOrtho(0, 0, width, height);
}else{
@@ -150,15 +157,15 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
Core.graphics.clear(Pal.darkerGray);
Draw.proj().setOrtho(0, 0, Core.graphics.getWidth(), Core.graphics.getHeight());
float height = UnitScl.dp.scl(50f);
float height = Scl.scl(50f);
Draw.color(Color.BLACK);
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);
Draw.color(Color.black);
Fill.rect(graphics.getWidth()/2f, graphics.getHeight()/2f, w, height);
Draw.color(Pal.accent);
@@ -170,13 +177,13 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
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);
font.draw((int)(assets.getProgress() * 100) + "%", graphics.getWidth() / 2f, graphics.getHeight() / 2f + Scl.scl(10f), Align.center);
font.draw(bundle.get("loading", "").replace("[accent]", ""), graphics.getWidth() / 2f, graphics.getHeight() / 2f + height / 2f + Scl.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);
font.draw(bundle.get("load." + key, ""), graphics.getWidth() / 2f, graphics.getHeight() / 2f - height / 2f - Scl.scl(10f), Align.center);
}
}
Draw.flush();

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry;
import io.anuke.arc.Application.*;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*;
@@ -10,9 +11,7 @@ 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.*;
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.*;
@@ -20,6 +19,7 @@ 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.plugin.*;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import java.nio.charset.*;
@@ -43,6 +43,8 @@ public class Vars implements Loadable{
public static final String discordURL = "https://discord.gg/mindustry";
/** URL for sending crash reports to */
public static final String crashReportURL = "http://mins.us.to/report";
/** list of built-in servers.*/
public static final Array<String> defaultServers = Array.with(/*"mins.us.to"*/);
/** maximum distance between mine and core that supports automatic transferring */
public static final float mineTransferRange = 220f;
/** team of the player by default */
@@ -59,14 +61,6 @@ public class Vars implements Loadable{
public static final float itemSize = 5f;
/** extra padding around the world; units outside this bound will begin to self-destruct. */
public static final float worldBounds = 100f;
/** default size of UI icons.*/
public static final int iconsize = 48;
/** size of UI icons (small)*/
public static final int iconsizesmall = 32;
/** size of UI icons (medium)*/
public static final int iconsizemed = 30;
/** size of UI icons (medium)*/
public static final int iconsizetiny = 16;
/** units outside of this bound will simply die instantly */
public static final float finalWorldBounds = worldBounds + 500;
/** ticks spent out of bound until self destruct. */
@@ -112,6 +106,8 @@ public class Vars implements Loadable{
public static boolean android;
/** whether the game is running on a headless server */
public static boolean headless;
/** whether steam is enabled for this game */
public static boolean steam;
/** application data directory, equivalent to {@link io.anuke.arc.Settings#getDataDirectory()} */
public static FileHandle dataDirectory;
/** data subdirectory used for screenshots */
@@ -126,8 +122,6 @@ public class Vars implements Loadable{
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 */
public static final String mapExtension = "msav";
/** save file extension */
@@ -136,13 +130,15 @@ public class Vars implements Loadable{
/** list of all locales that can be switched to */
public static Locale[] locales;
public static Net net;
public static ContentLoader content;
public static GameState state;
public static GlobalData data;
public static EntityCollisions collisions;
public static DefaultWaves defaultWaves;
public static LoopControl loops;
public static Platform platform;
public static Platform platform = new Platform(){};
public static Plugins plugins;
public static World world;
public static Maps maps;
@@ -168,7 +164,6 @@ public class Vars implements Loadable{
public static EntityGroup<Fire> fireGroup;
public static EntityGroup<BaseUnit>[] unitGroups;
/** all local players, currently only has one player. may be used for local co-op in the future */
public static Player player;
@Override
@@ -226,7 +221,7 @@ public class Vars implements Loadable{
for(EntityGroup<?> group : entities.all()){
group.setRemoveListener(entity -> {
if(entity instanceof SyncTrait && Net.client()){
if(entity instanceof SyncTrait && net.client()){
netClient.addRemovedEntity((entity).getID());
}
});
@@ -252,11 +247,16 @@ public class Vars implements Loadable{
public static void loadSettings(){
Core.settings.setAppName(appName);
if(steam){
Core.settings.setDataDirectory(Core.files.local("saves/"));
}
Core.settings.defaults("locale", "default");
Core.keybinds.setDefaults(Binding.values());
Core.settings.load();
UnitScl.dp.setProduct(settings.getInt("uiscale", 100) / 100f);
Scl.setProduct(settings.getInt("uiscale", 100) / 100f);
if(!loadLocales) return;
@@ -268,6 +268,7 @@ public class Vars implements Loadable{
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."));
}

View File

@@ -1,165 +1,261 @@
package io.anuke.mindustry.ai;
import io.anuke.arc.Events;
import io.anuke.arc.collection.IntArray;
import io.anuke.arc.collection.IntQueue;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.util.Structs;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.game.EventType.TileChangeEvent;
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.Teams.TeamData;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.Pos;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
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.async.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
public class Pathfinder{
public class Pathfinder implements Runnable{
private static final long maxUpdate = Time.millisToNanos(4);
private PathData[] paths;
private IntArray blocked = new IntArray();
private static final int updateFPS = 60;
private static final int updateInterval = 1000 / updateFPS;
private static final int impassable = -1;
/** tile data, see PathTileStruct */
private int[][] tiles;
/** unordered array of path data for iteration only. DO NOT iterate ot access this in the main thread.*/
private Array<PathData> list = new Array<>();
/** Maps teams + flags to a valid path to get to that flag for that team. */
private PathData[][] pathMap = new PathData[Team.all.length][PathTarget.all.length];
/** Grid map of created path data that should not be queued again. */
private GridBits created = new GridBits(Team.all.length, PathTarget.all.length);
/** handles task scheduling on the update thread. */
private TaskQueue queue = new TaskQueue();
/** current pathfinding thread */
private @Nullable Thread thread;
public Pathfinder(){
Events.on(WorldLoadEvent.class, event -> clear());
Events.on(TileChangeEvent.class, event -> {
if(Net.client()) return;
Events.on(WorldLoadEvent.class, event -> {
stop();
for(Team team : Team.all){
TeamData data = state.teams.get(team);
if(state.teams.isActive(team) && data.team != event.tile.getTeam()){
update(event.tile, data.team);
//reset and update internal tile array
tiles = new int[world.width()][world.height()];
pathMap = new PathData[Team.all.length][PathTarget.all.length];
created = new GridBits(Team.all.length, PathTarget.all.length);
list = new Array<>();
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
tiles[x][y] = packTile(world.rawTile(x, y));
}
}
update(event.tile, event.tile.getTeam());
//special preset which may help speed things up; this is optional
preloadPath(waveTeam, PathTarget.enemyCores);
start();
});
Events.on(ResetEvent.class, event -> stop());
Events.on(TileChangeEvent.class, event -> updateTile(event.tile));
}
/** Packs a tile into its internal representation. */
private int packTile(Tile tile){
return PathTile.get(tile.cost, tile.getTeamID(), (byte)0, (!tile.solid() || tile.breakable()) && tile.floor().drownTime <= 0f);
}
/** Starts or restarts the pathfinding thread. */
private void start(){
stop();
thread = Threads.daemon(this);
}
/** Stops the pathfinding thread. */
private void stop(){
if(thread != null){
thread.interrupt();
thread = null;
}
queue.clear();
}
/** Update a tile in the internal pathfinding grid. Causes a completely pathfinding reclaculation. */
public void updateTile(Tile tile){
if(net.client()) return;
int packed = packTile(tile);
int x = tile.x, y = tile.y;
tiles[x][y] = packed;
//can't iterate through array so use the map, which should not lead to problems
for(PathData[] arr : pathMap){
for(PathData path : arr){
if(path != null){
synchronized(path.targets){
path.targets.clear();
path.target.getTargets(path.team, path.targets);
}
}
}
}
queue.post(() -> {
for(PathData data : list){
updateTargets(data, x, y);
}
});
}
public void updateSolid(Tile tile){
update(tile, tile.getTeam());
}
/** Thread implementation. */
@Override
public void run(){
while(true){
if(net.client()) return;
public void update(){
if(Net.client() || paths == null) return;
queue.run();
for(Team team : Team.all){
if(state.teams.isActive(team)){
updateFrontier(team, maxUpdate);
//total update time no longer than maxUpdate
for(PathData data : list){
updateFrontier(data, maxUpdate / list.size);
}
try{
Thread.sleep(updateInterval);
}catch(InterruptedException e){
//stop looping when interrupted externally
return;
}
}
}
public Tile getTargetTile(Team team, Tile tile){
float[][] values = paths[team.ordinal()].weights;
/** Gets next tile to travel to. Main thread only. */
public Tile getTargetTile(Tile tile, Team team, PathTarget target){
if(tile == null) return null;
if(values == null || tile == null) return tile;
PathData data = pathMap[team.ordinal()][target.ordinal()];
float value = values[tile.x][tile.y];
if(data == null){
//if this combination is not found, create it on request
if(!created.get(team.ordinal(), target.ordinal())){
created.set(team.ordinal(), target.ordinal());
//grab targets since this is run on main thread
IntArray targets = target.getTargets(team, new IntArray());
queue.post(() -> createPath(team, target, targets));
}
return tile;
}
Tile target = null;
float tl = 0f;
int[][] values = data.weights;
int value = values[tile.x][tile.y];
Tile current = null;
int tl = 0;
for(Point2 point : Geometry.d8){
int dx = tile.x + point.x, dy = tile.y + point.y;
Tile other = world.tile(dx, dy);
if(other == null) continue;
if(values[dx][dy] < value && (target == null || values[dx][dy] < tl) &&
!other.solid() && other.floor().drownTime <= 0 &&
if(values[dx][dy] < value && (current == null || values[dx][dy] < tl) && !other.solid() && other.floor().drownTime <= 0 &&
!(point.x != 0 && point.y != 0 && (world.solid(tile.x + point.x, tile.y) || world.solid(tile.x, tile.y + point.y)))){ //diagonal corner trap
target = other;
current = other;
tl = values[dx][dy];
}
}
if(target == null || tl == Float.MAX_VALUE) return tile;
if(current == null || tl == impassable) return tile;
return target;
return current;
}
public float getValueforTeam(Team team, int x, int y){
return paths == null || paths[team.ordinal()].weights == null || team.ordinal() >= paths.length ? 0 : Structs.inBounds(x, y, paths[team.ordinal()].weights) ? paths[team.ordinal()].weights[x][y] : 0;
}
private boolean passable(Tile tile, Team team){
return (!tile.solid()) || (tile.breakable() && (tile.getTeam() != team));
/** @return whether a tile can be passed through by this team. Pathfinding thread only.*/
private boolean passable(int x, int y, Team team){
int tile = tiles[x][y];
return PathTile.passable(tile) || (PathTile.team(tile) != team.ordinal() && PathTile.team(tile) != Team.derelict.ordinal());
}
/**
* Clears the frontier, increments the search and sets up all flow sources.
* This only occurs for active teams.
*/
private void update(Tile tile, Team team){
//make sure team exists
if(paths != null && paths[team.ordinal()] != null && paths[team.ordinal()].weights != null && Structs.inBounds(tile.x, tile.y, paths[team.ordinal()].weights)){
PathData path = paths[team.ordinal()];
private void updateTargets(PathData path, int x, int y){
if(!Structs.inBounds(x, y, path.weights)) return;
if(path.weights[tile.x][tile.y] <= 0.1f){
//this was a previous target
path.frontier.clear();
}else if(!path.frontier.isEmpty()){
return;
}
//impassable tiles have a weight of float.max
if(!passable(tile, team)){
path.weights[tile.x][tile.y] = Float.MAX_VALUE;
}
//increment search, clear frontier
path.search++;
if(path.weights[x][y] == 0){
//this was a previous target
path.frontier.clear();
path.lastSearchTime = Time.millis();
}else if(!path.frontier.isEmpty()){
//skip if this path is processing
return;
}
//add all targets to the frontier
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());
//assign impassability to the tile
if(!passable(x, y, path.team)){
path.weights[x][y] = impassable;
}
//increment search, clear frontier
path.search++;
path.frontier.clear();
synchronized(path.targets){
//add targets
for(int i = 0; i < path.targets.size; i++){
int pos = path.targets.get(i);
int tx = Pos.x(pos), ty = Pos.y(pos);
path.weights[tx][ty] = 0;
path.searches[tx][ty] = (short)path.search;
path.frontier.addFirst(pos);
}
}
}
private void createFor(Team team){
PathData path = new PathData();
path.weights = new float[world.width()][world.height()];
path.searches = new short[world.width()][world.height()];
path.search++;
path.frontier.ensureCapacity((world.width() + world.height()) * 3);
private void preloadPath(Team team, PathTarget target){
updateFrontier(createPath(team, target, target.getTargets(team, new IntArray())), -1);
}
paths[team.ordinal()] = path;
/** Created a new flowfield that aims to get to a certain target for a certain team.
* Pathfinding thread only. */
private PathData createPath(Team team, PathTarget target, IntArray targets){
PathData path = new PathData(team, target, world.width(), world.height());
list.add(path);
pathMap[team.ordinal()][target.ordinal()] = path;
//grab targets from passed array
synchronized(path.targets){
path.targets.clear();
path.targets.addAll(targets);
}
//fill with impassables by default
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
Tile tile = world.tile(x, y);
if(state.teams.areEnemies(tile.getTeam(), team)
&& tile.block().flags.contains(BlockFlag.target)){
path.frontier.addFirst(tile.pos());
path.weights[x][y] = 0;
path.searches[x][y] = (short)path.search;
}else{
path.weights[x][y] = Float.MAX_VALUE;
}
path.weights[x][y] = impassable;
}
}
updateFrontier(team, -1);
//add targets
for(int i = 0; i < path.targets.size; i++){
int pos = path.targets.get(i);
path.weights[Pos.x(pos)][Pos.y(pos)] = 0;
path.frontier.addFirst(pos);
}
return path;
}
private void updateFrontier(Team team, long nsToRun){
PathData path = paths[team.ordinal()];
/** Update the frontier for a path. Pathfinding thread only. */
private void updateFrontier(PathData path, long nsToRun){
long start = Time.nanos();
while(path.frontier.size > 0 && (nsToRun < 0 || Time.timeSinceNanos(start) <= nsToRun)){
Tile tile = world.tile(path.frontier.removeLast());
if(tile == null || path.weights == null) return; //something went horribly wrong, bail
float cost = path.weights[tile.x][tile.y];
int cost = path.weights[tile.x][tile.y];
//pathfinding overflowed for some reason, time to bail. the next block update will handle this, hopefully
if(path.frontier.size >= world.width() * world.height()){
@@ -167,14 +263,13 @@ public class Pathfinder{
return;
}
if(cost < Float.MAX_VALUE){
if(cost != impassable){
for(Point2 point : Geometry.d4){
int dx = tile.x + point.x, dy = tile.y + point.y;
Tile other = world.tile(dx, dy);
if(other != null && (path.weights[dx][dy] > cost + other.cost || path.searches[dx][dy] < path.search)
&& passable(other, team)){
if(other != null && (path.weights[dx][dy] > cost + other.cost || path.searches[dx][dy] < path.search) && passable(dx, dy, path.team)){
if(other.cost < 0) throw new IllegalArgumentException("Tile cost cannot be negative! " + other);
path.frontier.addFirst(Pos.get(dx, dy));
path.weights[dx][dy] = cost + other.cost;
@@ -185,27 +280,78 @@ public class Pathfinder{
}
}
private void clear(){
Time.mark();
paths = new PathData[Team.all.length];
blocked.clear();
for(Team team : Team.all){
PathData path = new PathData();
paths[team.ordinal()] = path;
if(state.teams.isActive(team)){
createFor(team);
/** A path target defines a set of targets for a path.*/
public enum PathTarget{
enemyCores((team, out) -> {
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
out.add(other.pos());
}
//spawn points are also enemies.
if(state.rules.waves && team == defaultTeam){
for(Tile other : spawner.getGroundSpawns()){
out.add(other.pos());
}
}
}),
rallyPoints((team, out) -> {
for(Tile other : indexer.getAllied(team, BlockFlag.rally)){
out.add(other.pos());
}
});
public static final PathTarget[] all = values();
private final BiConsumer<Team, IntArray> targeter;
PathTarget(BiConsumer<Team, IntArray> targeter){
this.targeter = targeter;
}
/** Get targets. This must run on the main thread.*/
public IntArray getTargets(Team team, IntArray out){
targeter.accept(team, out);
return out;
}
}
/** Data for a specific flow field to some set of destinations. */
class PathData{
float[][] weights;
short[][] searches;
int search = 0;
long lastSearchTime;
IntQueue frontier = new IntQueue();
/** Team this path is for. */
final Team team;
/** Flag that is being targeted. */
final PathTarget target;
/** costs of getting to a specific tile */
final int[][] weights;
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
final short[][] searches;
/** search frontier, these are Pos objects */
final IntQueue frontier = new IntQueue();
/** all target positions; these positions have a cost of 0, and must be synchronized on! */
final IntArray targets = new IntArray();
/** current search ID */
int search = 1;
PathData(Team team, PathTarget target, int width, int height){
this.team = team;
this.target = target;
this.weights = new int[width][height];
this.searches = new short[width][height];
this.frontier.ensureCapacity((width + height) * 3);
}
}
/** Holds a copy of tile data for a specific tile position. */
@Struct
class PathTileStruct{
//traversal cost
byte cost;
//team of block, if applicable (0 by default)
byte team;
//type of target; TODO remove
byte type;
//whether it's viable to pass this block
boolean passable;
}
}

View File

@@ -14,7 +14,6 @@ import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
import io.anuke.mindustry.game.SpawnGroup;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.Tile;
import static io.anuke.mindustry.Vars.*;
@@ -116,7 +115,7 @@ public class WaveSpawner{
}
public boolean isSpawning(){
return spawning && !Net.client();
return spawning && !net.client();
}
private void reset(){

View File

@@ -1,6 +1,7 @@
package io.anuke.mindustry.content;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
@@ -8,6 +9,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.Bullet;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
@@ -501,6 +503,7 @@ public class Blocks implements ContentList{
consumes.items(new ItemStack(Items.thorium, 4), new ItemStack(Items.sand, 10));
consumes.power(5f);
itemCapacity = 20;
int bottomRegion = reg("-bottom"), weaveRegion = reg("-weave");
@@ -590,7 +593,7 @@ public class Blocks implements ContentList{
pyratiteMixer = new GenericSmelter("pyratite-mixer"){{
requirements(Category.crafting, ItemStack.with(Items.copper, 50, Items.lead, 25));
flameColor = Color.CLEAR;
flameColor = Color.clear;
hasItems = true;
hasPower = true;
outputItem = new ItemStack(Items.pyratite, 1);
@@ -661,7 +664,7 @@ public class Blocks implements ContentList{
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.rect(reg(frameRegions[(int)Mathf.absin(entity.totalProgress, 5f, 2.999f)]), tile.drawx(), tile.drawy());
Draw.color(Color.CLEAR, tile.entity.liquids.current().color, tile.entity.liquids.total() / liquidCapacity);
Draw.color(Color.clear, tile.entity.liquids.current().color, tile.entity.liquids.total() / liquidCapacity);
Draw.rect(reg(liquidRegion), tile.drawx(), tile.drawy());
Draw.color();
Draw.rect(reg(topRegion), tile.drawx(), tile.drawy());
@@ -986,7 +989,6 @@ public class Blocks implements ContentList{
pulseConduit = new Conduit("pulse-conduit"){{
requirements(Category.liquid, ItemStack.with(Items.titanium, 1, Items.metaglass, 1));
liquidCapacity = 16f;
liquidFlowFactor = 4.9f;
health = 90;
}};
@@ -1401,7 +1403,7 @@ public class Blocks implements ContentList{
smokeEffect = Fx.lancerLaserShootSmoke;
chargeEffect = Fx.lancerLaserCharge;
chargeBeginEffect = Fx.lancerLaserChargeBegin;
heatColor = Color.RED;
heatColor = Color.red;
size = 2;
health = 280 * size * size;
targetAir = false;
@@ -1409,16 +1411,16 @@ public class Blocks implements ContentList{
}};
arc = new PowerTurret("arc"){{
requirements(Category.turret, ItemStack.with(Items.copper, 35, Items.lead, 35));
requirements(Category.turret, ItemStack.with(Items.copper, 35, Items.lead, 50));
shootType = Bullets.arc;
reload = 24f;
reload = 35f;
shootCone = 40f;
rotatespeed = 8f;
powerUse = 0.9f;
powerUse = 1.5f;
targetAir = false;
range = 95f;
range = 90f;
shootEffect = Fx.lightningShoot;
heatColor = Color.RED;
heatColor = Color.red;
recoil = 1f;
size = 1;
health = 260;
@@ -1497,7 +1499,7 @@ public class Blocks implements ContentList{
}
@Override
public void init(Bullet b){
public void init(io.anuke.mindustry.entities.type.Bullet b){
for(int i = 0; i < rays; i++){
Damage.collideLine(b, b.getTeam(), hitEffect, b.x, b.y, b.rot(), rayLength - Math.abs(i - (rays / 2)) * 20f);
}
@@ -1506,7 +1508,7 @@ public class Blocks implements ContentList{
@Override
public void draw(Bullet b){
super.draw(b);
Draw.color(Color.WHITE, Pal.lancerLaser, b.fin());
Draw.color(Color.white, Pal.lancerLaser, b.fin());
//Draw.alpha(b.fout());
for(int i = 0; i < 7; i++){
Tmp.v1.trns(b.rot(), i * 8f);
@@ -1649,6 +1651,7 @@ public class Blocks implements ContentList{
commandCenter = new CommandCenter("command-center"){{
requirements(Category.units, ItemStack.with(Items.copper, 200, Items.lead, 250, Items.silicon, 250, Items.graphite, 100));
flags = EnumSet.of(BlockFlag.rally, BlockFlag.comandCenter);
size = 2;
health = size * size * 55;
}};
@@ -1779,4 +1782,4 @@ public class Blocks implements ContentList{
//endregion
}
}
}

View File

@@ -8,6 +8,7 @@ import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
@@ -250,7 +251,7 @@ public class Bullets implements ContentList{
splashDamageRadius = 25f;
splashDamage = 10f;
lifetime = 120f;
trailColor = Color.GRAY;
trailColor = Color.gray;
backColor = Pal.bulletYellowBack;
frontColor = Pal.bulletYellow;
hitEffect = Fx.blastExplosion;
@@ -396,7 +397,7 @@ public class Bullets implements ContentList{
Draw.color(Pal.heal);
Lines.stroke(2f);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 7f);
Draw.color(Color.WHITE);
Draw.color(Color.white);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 3f);
Draw.reset();
}
@@ -430,7 +431,7 @@ public class Bullets implements ContentList{
@Override
public void draw(Bullet b){
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.GRAY, b.fin());
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.gray, b.fin());
Fill.circle(b.x, b.y, 3f * b.fout());
Draw.reset();
}
@@ -498,7 +499,7 @@ public class Bullets implements ContentList{
};
lancerLaser = new BulletType(0.001f, 140){
Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.WHITE};
Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
float[] tscales = {1f, 0.7f, 0.5f, 0.2f};
float[] lenscales = {1f, 1.1f, 1.13f, 1.14f};
float length = 160f;
@@ -540,7 +541,7 @@ public class Bullets implements ContentList{
meltdownLaser = new BulletType(0.001f, 70){
Color tmpColor = new Color();
Color[] colors = {Color.valueOf("ec745855"), Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.WHITE};
Color[] colors = {Color.valueOf("ec745855"), Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
float[] tscales = {1f, 0.7f, 0.5f, 0.2f};
float[] strokes = {2f, 1.5f, 1f, 0.3f};
float[] lenscales = {1f, 1.12f, 1.15f, 1.17f};
@@ -636,7 +637,7 @@ public class Bullets implements ContentList{
}
};
arc = new BulletType(0.001f, 25){
arc = new BulletType(0.001f, 21){
{
lifetime = 1;
despawnEffect = Fx.none;
@@ -688,7 +689,7 @@ public class Bullets implements ContentList{
bulletHeight = 12f;
hitEffect = Fx.pulverize;
backColor = new Color(0x4f4f4fff);
frontColor = Color.GRAY;
frontColor = Color.gray;
}
@Override

View File

@@ -1,19 +1,17 @@
package io.anuke.mindustry.content;
import io.anuke.arc.Core;
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.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Tmp;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.effect.GroundEffectEntity.GroundEffect;
import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.graphics.Drawf;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.Item.Icon;
import io.anuke.arc.math.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.effect.GroundEffectEntity.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.Item.*;
import static io.anuke.mindustry.Vars.tilesize;
@@ -32,7 +30,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, commandSend;
unitSpawn, spawnShockwave, magmasmoke, impactShockwave, impactcloud, impactsmoke, dynamicExplosion, padlaunch, commandSend, coreLand;
@Override
public void load(){
@@ -93,14 +91,14 @@ public class Fx implements ContentList{
});
smoke = new Effect(100, e -> {
Draw.color(Color.GRAY, Pal.darkishGray, e.fin());
Draw.color(Color.gray, Pal.darkishGray, e.fin());
float size = 7f - e.fin() * 7f;
Draw.rect("circle", e.x, e.y, size, size);
Draw.reset();
});
magmasmoke = new Effect(110, e -> {
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Fill.circle(e.x, e.y, e.fslope() * 6f);
Draw.reset();
});
@@ -180,7 +178,7 @@ public class Fx implements ContentList{
hitBulletSmall = new Effect(14, e -> {
Draw.color(Color.WHITE, Pal.lightOrange, e.fin());
Draw.color(Color.white, Pal.lightOrange, e.fin());
e.scaled(7f, s -> {
Lines.stroke(0.5f + s.fout());
@@ -199,7 +197,7 @@ public class Fx implements ContentList{
});
hitFuse = new Effect(14, e -> {
Draw.color(Color.WHITE, Pal.surge, e.fin());
Draw.color(Color.white, Pal.surge, e.fin());
e.scaled(7f, s -> {
Lines.stroke(0.5f + s.fout());
@@ -218,7 +216,7 @@ public class Fx implements ContentList{
});
hitBulletBig = new Effect(13, e -> {
Draw.color(Color.WHITE, Pal.lightOrange, e.fin());
Draw.color(Color.white, Pal.lightOrange, e.fin());
Lines.stroke(0.5f + e.fout() * 1.5f);
Angles.randLenVectors(e.id, 8, e.finpow() * 30f, e.rotation, 50f, (x, y) -> {
@@ -252,7 +250,7 @@ public class Fx implements ContentList{
});
hitLancer = new Effect(12, e -> {
Draw.color(Color.WHITE);
Draw.color(Color.white);
Lines.stroke(e.fout() * 1.5f);
Angles.randLenVectors(e.id, 8, e.finpow() * 17f, e.rotation, 360f, (x, y) -> {
@@ -276,14 +274,14 @@ public class Fx implements ContentList{
});
hitLaser = new Effect(8, e -> {
Draw.color(Color.WHITE, Pal.heal, e.fin());
Draw.color(Color.white, Pal.heal, e.fin());
Lines.stroke(0.5f + e.fout());
Lines.circle(e.x, e.y, e.fin() * 5f);
Draw.reset();
});
despawn = new Effect(12, e -> {
Draw.color(Pal.lighterOrange, Color.GRAY, e.fin());
Draw.color(Pal.lighterOrange, Color.gray, e.fin());
Lines.stroke(e.fout());
Angles.randLenVectors(e.id, 7, e.fin() * 7f, e.rotation, 40f, (x, y) -> {
@@ -302,7 +300,7 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, 3f + i.fin() * 10f);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 5, 2f + 23f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f);
@@ -326,7 +324,7 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, 3f + i.fin() * 24f);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 7, 2f + 28f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f);
@@ -350,7 +348,7 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, 3f + i.fin() * 34f);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 7, 2f + 30f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f);
@@ -374,7 +372,7 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, 3f + i.fin() * 15f);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 5, 2f + 23f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f);
@@ -423,7 +421,7 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, 3f + i.fin() * 25f);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 6, 2f + 23f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f);
@@ -461,7 +459,7 @@ public class Fx implements ContentList{
});
fireSmoke = new Effect(35f, e -> {
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 1, 2f + e.fin() * 7f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, 0.2f + e.fslope() * 1.5f);
@@ -471,7 +469,7 @@ public class Fx implements ContentList{
});
steam = new Effect(35f, e -> {
Draw.color(Color.LIGHT_GRAY);
Draw.color(Color.lightGray);
Angles.randLenVectors(e.id, 2, 2f + e.fin() * 7f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, 0.2f + e.fslope() * 1.5f);
@@ -481,7 +479,7 @@ public class Fx implements ContentList{
});
fireballsmoke = new Effect(25f, e -> {
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 1, 2f + e.fin() * 7f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, 0.2f + e.fout() * 1.5f);
@@ -511,7 +509,7 @@ public class Fx implements ContentList{
});
melting = new Effect(40f, e -> {
Draw.color(Liquids.slag.color, Color.WHITE, e.fout() / 5f + Mathf.randomSeedRange(e.id, 0.12f));
Draw.color(Liquids.slag.color, Color.white, e.fout() / 5f + Mathf.randomSeedRange(e.id, 0.12f));
Angles.randLenVectors(e.id, 2, 1f + e.fin() * 3f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, .2f + e.fout() * 1.2f);
@@ -559,35 +557,35 @@ public class Fx implements ContentList{
shockwave = new Effect(10f, 80f, e -> {
Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin());
Draw.color(Color.white, Color.lightGray, e.fin());
Lines.stroke(e.fout() * 2f + 0.2f);
Lines.circle(e.x, e.y, e.fin() * 28f);
Draw.reset();
});
bigShockwave = new Effect(10f, 80f, e -> {
Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin());
Draw.color(Color.white, Color.lightGray, e.fin());
Lines.stroke(e.fout() * 3f);
Lines.circle(e.x, e.y, e.fin() * 50f);
Draw.reset();
});
nuclearShockwave = new Effect(10f, 200f, e -> {
Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin());
Draw.color(Color.white, Color.lightGray, e.fin());
Lines.stroke(e.fout() * 3f + 0.2f);
Lines.circle(e.x, e.y, e.fin() * 140f);
Draw.reset();
});
impactShockwave = new Effect(13f, 300f, e -> {
Draw.color(Pal.lighterOrange, Color.LIGHT_GRAY, e.fin());
Draw.color(Pal.lighterOrange, Color.lightGray, e.fin());
Lines.stroke(e.fout() * 4f + 0.2f);
Lines.circle(e.x, e.y, e.fin() * 200f);
Draw.reset();
});
spawnShockwave = new Effect(20f, 400f, e -> {
Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin());
Draw.color(Color.white, Color.lightGray, e.fin());
Lines.stroke(e.fout() * 3f + 0.5f);
Lines.circle(e.x, e.y, e.fin() * (e.rotation + 50f));
Draw.reset();
@@ -599,14 +597,14 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, 3f + i.fin() * 10f);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 6, 2f + 19f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f);
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f);
});
Draw.color(Pal.lighterOrange, Pal.lightOrange, Color.GRAY, e.fin());
Draw.color(Pal.lighterOrange, Pal.lightOrange, Color.gray, e.fin());
Lines.stroke(1.5f * e.fout());
Angles.randLenVectors(e.id + 1, 8, 1f + 23f * e.finpow(), (x, y) -> {
@@ -624,14 +622,14 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, (3f + i.fin() * 14f) * intensity);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, e.finpow(), (int)(6 * intensity), 21f * intensity, (x, y, in, out) -> {
Fill.circle(e.x + x, e.y + y, out * (2f + intensity) * 3 + 0.5f);
Fill.circle(e.x + x / 2f, e.y + y / 2f, out * (intensity) * 3);
});
Draw.color(Pal.lighterOrange, Pal.lightOrange, Color.GRAY, e.fin());
Draw.color(Pal.lighterOrange, Pal.lightOrange, Color.gray, e.fin());
Lines.stroke((1.7f * e.fout()) * (1f + (intensity - 1f) / 2f));
Angles.randLenVectors(e.id + 1, e.finpow(), (int)(9 * intensity), 40f * intensity, (x, y, in, out) -> {
@@ -647,14 +645,14 @@ public class Fx implements ContentList{
Lines.circle(e.x, e.y, 3f + i.fin() * 14f);
});
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 6, 2f + 19f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f);
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f);
});
Draw.color(Pal.lighterOrange, Pal.lightOrange, Color.GRAY, e.fin());
Draw.color(Pal.lighterOrange, Pal.lightOrange, Color.gray, e.fin());
Lines.stroke(1.7f * e.fout());
Angles.randLenVectors(e.id + 1, 9, 1f + 23f * e.finpow(), (x, y) -> {
@@ -665,7 +663,7 @@ public class Fx implements ContentList{
});
blockExplosionSmoke = new Effect(30, e -> {
Draw.color(Color.GRAY);
Draw.color(Color.gray);
Angles.randLenVectors(e.id, 6, 4f + 30f * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 3f);
@@ -693,7 +691,7 @@ public class Fx implements ContentList{
});
shootSmallSmoke = new Effect(20f, e -> {
Draw.color(Pal.lighterOrange, Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Pal.lighterOrange, Color.lightGray, Color.gray, e.fin());
Angles.randLenVectors(e.id, 5, e.finpow() * 6f, e.rotation, 20f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 1.5f);
@@ -711,7 +709,7 @@ public class Fx implements ContentList{
});
shootBig2 = new Effect(10, e -> {
Draw.color(Pal.lightOrange, Color.GRAY, e.fin());
Draw.color(Pal.lightOrange, Color.gray, e.fin());
float w = 1.2f + 8 * e.fout();
Drawf.tri(e.x, e.y, w, 29f * e.fout(), e.rotation);
Drawf.tri(e.x, e.y, w, 5f * e.fout(), e.rotation + 180f);
@@ -719,7 +717,7 @@ public class Fx implements ContentList{
});
shootBigSmoke = new Effect(17f, e -> {
Draw.color(Pal.lighterOrange, Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Pal.lighterOrange, Color.lightGray, Color.gray, e.fin());
Angles.randLenVectors(e.id, 8, e.finpow() * 19f, e.rotation, 10f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 2f + 0.2f);
@@ -729,7 +727,7 @@ public class Fx implements ContentList{
});
shootBigSmoke2 = new Effect(18f, e -> {
Draw.color(Pal.lightOrange, Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Pal.lightOrange, Color.lightGray, Color.gray, e.fin());
Angles.randLenVectors(e.id, 9, e.finpow() * 23f, e.rotation, 20f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 2.4f + 0.2f);
@@ -739,7 +737,7 @@ public class Fx implements ContentList{
});
shootSmallFlame = new Effect(32f, e -> {
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.GRAY, e.fin());
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.gray, e.fin());
Angles.randLenVectors(e.id, 8, e.finpow() * 60f, e.rotation, 10f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, 0.65f + e.fout() * 1.5f);
@@ -749,7 +747,7 @@ public class Fx implements ContentList{
});
shootPyraFlame = new Effect(33f, e -> {
Draw.color(Pal.lightPyraFlame, Pal.darkPyraFlame, Color.GRAY, e.fin());
Draw.color(Pal.lightPyraFlame, Pal.darkPyraFlame, Color.gray, e.fin());
Angles.randLenVectors(e.id, 10, e.finpow() * 70f, e.rotation, 10f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, 0.65f + e.fout() * 1.6f);
@@ -759,7 +757,7 @@ public class Fx implements ContentList{
});
shootLiquid = new Effect(40f, e -> {
Draw.color(e.color, Color.WHITE, e.fout() / 6f + Mathf.randomSeedRange(e.id, 0.1f));
Draw.color(e.color, Color.white, e.fout() / 6f + Mathf.randomSeedRange(e.id, 0.1f));
Angles.randLenVectors(e.id, 6, e.finpow() * 60f, e.rotation, 11f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, 0.5f + e.fout() * 2.5f);
@@ -769,7 +767,7 @@ public class Fx implements ContentList{
});
shellEjectSmall = new GroundEffect(30f, 400f, e -> {
Draw.color(Pal.lightOrange, Color.LIGHT_GRAY, Pal.lightishGray, e.fin());
Draw.color(Pal.lightOrange, Color.lightGray, Pal.lightishGray, e.fin());
float rot = Math.abs(e.rotation) + 90f;
int i = Mathf.sign(e.rotation);
@@ -784,7 +782,7 @@ public class Fx implements ContentList{
});
shellEjectMedium = new GroundEffect(34f, 400f, e -> {
Draw.color(Pal.lightOrange, Color.LIGHT_GRAY, Pal.lightishGray, e.fin());
Draw.color(Pal.lightOrange, Color.lightGray, Pal.lightishGray, e.fin());
float rot = e.rotation + 90f;
for(int i : Mathf.signs){
float len = (2f + e.finpow() * 10f) * i;
@@ -795,7 +793,7 @@ public class Fx implements ContentList{
2f, 3f, rot);
}
Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Color.lightGray, Color.gray, e.fin());
for(int i : Mathf.signs){
float ex = e.x, ey = e.y, fout = e.fout();
@@ -808,7 +806,7 @@ public class Fx implements ContentList{
});
shellEjectBig = new GroundEffect(22f, 400f, e -> {
Draw.color(Pal.lightOrange, Color.LIGHT_GRAY, Pal.lightishGray, e.fin());
Draw.color(Pal.lightOrange, Color.lightGray, Pal.lightishGray, e.fin());
float rot = e.rotation + 90f;
for(int i : Mathf.signs){
float len = (4f + e.finpow() * 8f) * i;
@@ -820,7 +818,7 @@ public class Fx implements ContentList{
rot + e.fin() * 30f * i + Mathf.randomSeedRange(e.id + i + 9, 40f * e.fin()));
}
Draw.color(Color.LIGHT_GRAY);
Draw.color(Color.lightGray);
for(int i : Mathf.signs){
float ex = e.x, ey = e.y, fout = e.fout();
@@ -881,7 +879,7 @@ public class Fx implements ContentList{
});
lightningShoot = new Effect(12f, e -> {
Draw.color(Color.WHITE, Pal.lancerLaser, e.fin());
Draw.color(Color.white, Pal.lancerLaser, e.fin());
Lines.stroke(e.fout() * 1.2f + 0.5f);
Angles.randLenVectors(e.id, 7, 25f * e.finpow(), e.rotation, 50f, (x, y) -> {
@@ -895,7 +893,7 @@ public class Fx implements ContentList{
reactorsmoke = new Effect(17, e -> {
Angles.randLenVectors(e.id, 4, e.fin() * 8f, (x, y) -> {
float size = 1f + e.fout() * 5f;
Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Color.lightGray, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, size, size);
Draw.reset();
});
@@ -903,7 +901,7 @@ public class Fx implements ContentList{
nuclearsmoke = new Effect(40, e -> {
Angles.randLenVectors(e.id, 4, e.fin() * 13f, (x, y) -> {
float size = e.fslope() * 4f;
Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Color.lightGray, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, size, size);
Draw.reset();
});
@@ -911,7 +909,7 @@ public class Fx implements ContentList{
nuclearcloud = new Effect(90, 200f, e -> {
Angles.randLenVectors(e.id, 10, e.finpow() * 90f, (x, y) -> {
float size = e.fout() * 14f;
Draw.color(Color.LIME, Color.GRAY, e.fin());
Draw.color(Color.lime, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, size, size);
Draw.reset();
});
@@ -919,7 +917,7 @@ public class Fx implements ContentList{
impactsmoke = new Effect(60, e -> {
Angles.randLenVectors(e.id, 7, e.fin() * 20f, (x, y) -> {
float size = e.fslope() * 4f;
Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Color.lightGray, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, size, size);
Draw.reset();
});
@@ -927,7 +925,7 @@ public class Fx implements ContentList{
impactcloud = new Effect(140, 400f, e -> {
Angles.randLenVectors(e.id, 20, e.finpow() * 160f, (x, y) -> {
float size = e.fout() * 15f;
Draw.color(Pal.lighterOrange, Color.LIGHT_GRAY, e.fin());
Draw.color(Pal.lighterOrange, Color.lightGray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, size, size);
Draw.reset();
});
@@ -935,7 +933,7 @@ public class Fx implements ContentList{
redgeneratespark = new Effect(18, e -> {
Angles.randLenVectors(e.id, 5, e.fin() * 8f, (x, y) -> {
float len = e.fout() * 4f;
Draw.color(Pal.redSpark, Color.GRAY, e.fin());
Draw.color(Pal.redSpark, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, len, len);
Draw.reset();
});
@@ -943,7 +941,7 @@ public class Fx implements ContentList{
generatespark = new Effect(18, e -> {
Angles.randLenVectors(e.id, 5, e.fin() * 8f, (x, y) -> {
float len = e.fout() * 4f;
Draw.color(Pal.orangeSpark, Color.GRAY, e.fin());
Draw.color(Pal.orangeSpark, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, len, len);
Draw.reset();
});
@@ -951,14 +949,14 @@ public class Fx implements ContentList{
fuelburn = new Effect(23, e -> {
Angles.randLenVectors(e.id, 5, e.fin() * 9f, (x, y) -> {
float len = e.fout() * 4f;
Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin());
Draw.color(Color.lightGray, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, len, len);
Draw.reset();
});
});
plasticburn = new Effect(40, e -> {
Angles.randLenVectors(e.id, 5, 3f + e.fin() * 5f, (x, y) -> {
Draw.color(Color.valueOf("e9ead3"), Color.GRAY, e.fin());
Draw.color(Color.valueOf("e9ead3"), Color.gray, e.fin());
Fill.circle(e.x + x, e.y + y, e.fout() * 1f);
Draw.reset();
});
@@ -1000,21 +998,21 @@ public class Fx implements ContentList{
});
producesmoke = new Effect(12, e -> {
Angles.randLenVectors(e.id, 8, 4f + e.fin() * 18f, (x, y) -> {
Draw.color(Color.WHITE, Pal.accent, e.fin());
Draw.color(Color.white, Pal.accent, e.fin());
Fill.square(e.x + x, e.y + y, 1f + e.fout() * 3f, 45);
Draw.reset();
});
});
smeltsmoke = new Effect(15, e -> {
Angles.randLenVectors(e.id, 6, 4f + e.fin() * 5f, (x, y) -> {
Draw.color(Color.WHITE, e.color, e.fin());
Draw.color(Color.white, e.color, e.fin());
Fill.square(e.x + x, e.y + y, 0.5f + e.fout() * 2f, 45);
Draw.reset();
});
});
formsmoke = new Effect(40, e -> {
Angles.randLenVectors(e.id, 6, 5f + e.fin() * 8f, (x, y) -> {
Draw.color(Pal.plasticSmoke, Color.LIGHT_GRAY, e.fin());
Draw.color(Pal.plasticSmoke, Color.lightGray, e.fin());
Fill.square(e.x + x, e.y + y, 0.2f + e.fout() * 2f, 45);
Draw.reset();
});
@@ -1022,7 +1020,7 @@ public class Fx implements ContentList{
blastsmoke = new Effect(26, e -> {
Angles.randLenVectors(e.id, 12, 1f + e.fin() * 23f, (x, y) -> {
float size = 2f + e.fout() * 6f;
Draw.color(Color.LIGHT_GRAY, Color.DARK_GRAY, e.fin());
Draw.color(Color.lightGray, Color.darkGray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, size, size);
Draw.reset();
});
@@ -1030,7 +1028,7 @@ public class Fx implements ContentList{
lava = new Effect(18, e -> {
Angles.randLenVectors(e.id, 3, 1f + e.fin() * 10f, (x, y) -> {
float size = e.fslope() * 4f;
Draw.color(Color.ORANGE, Color.GRAY, e.fin());
Draw.color(Color.orange, Color.gray, e.fin());
Draw.rect("circle", e.x + x, e.y + y, size, size);
Draw.reset();
});
@@ -1056,53 +1054,53 @@ public class Fx implements ContentList{
Draw.reset();
});
purify = new Effect(10, e -> {
Draw.color(Color.ROYAL, Color.GRAY, e.fin());
Draw.color(Color.royal, Color.gray, e.fin());
Lines.stroke(2f);
Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6);
Draw.reset();
});
purifyoil = new Effect(10, e -> {
Draw.color(Color.BLACK, Color.GRAY, e.fin());
Draw.color(Color.black, Color.gray, e.fin());
Lines.stroke(2f);
Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6);
Draw.reset();
});
purifystone = new Effect(10, e -> {
Draw.color(Color.ORANGE, Color.GRAY, e.fin());
Draw.color(Color.orange, Color.gray, e.fin());
Lines.stroke(2f);
Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6);
Draw.reset();
});
generate = new Effect(11, e -> {
Draw.color(Color.ORANGE, Color.YELLOW, e.fin());
Draw.color(Color.orange, Color.yellow, e.fin());
Lines.stroke(1f);
Lines.spikes(e.x, e.y, e.fin() * 5f, 2, 8);
Draw.reset();
});
mine = new Effect(20, e -> {
Angles.randLenVectors(e.id, 6, 3f + e.fin() * 6f, (x, y) -> {
Draw.color(e.color, Color.LIGHT_GRAY, e.fin());
Draw.color(e.color, Color.lightGray, e.fin());
Fill.square(e.x + x, e.y + y, e.fout() * 2f, 45);
Draw.reset();
});
});
mineBig = new Effect(30, e -> {
Angles.randLenVectors(e.id, 6, 4f + e.fin() * 8f, (x, y) -> {
Draw.color(e.color, Color.LIGHT_GRAY, e.fin());
Draw.color(e.color, Color.lightGray, e.fin());
Fill.square(e.x + x, e.y + y, e.fout() * 2f + 0.2f, 45);
Draw.reset();
});
});
mineHuge = new Effect(40, e -> {
Angles.randLenVectors(e.id, 8, 5f + e.fin() * 10f, (x, y) -> {
Draw.color(e.color, Color.LIGHT_GRAY, e.fin());
Draw.color(e.color, Color.lightGray, e.fin());
Fill.square(e.x + x, e.y + y, e.fout() * 2f + 0.5f, 45);
Draw.reset();
});
});
smelt = new Effect(20, e -> {
Angles.randLenVectors(e.id, 6, 2f + e.fin() * 5f, (x, y) -> {
Draw.color(Color.WHITE, e.color, e.fin());
Draw.color(Color.white, e.color, e.fin());
Fill.square(e.x + x, e.y + y, 0.5f + e.fout() * 2f, 45);
Draw.reset();
});
@@ -1209,5 +1207,8 @@ public class Fx implements ContentList{
Lines.poly(e.x, e.y, 6, e.rotation + e.fin(), 90);
Draw.reset();
});
coreLand = new Effect(120f, e -> {
});
}
}

View File

@@ -39,7 +39,7 @@ public class Items implements ContentList{
}};
coal = new Item("coal", Color.valueOf("272727")){{
explosiveness = 0.4f;
explosiveness = 0.2f;
flammability = 1f;
hardness = 2;
}};

View File

@@ -1,10 +1,14 @@
package io.anuke.mindustry.content;
import io.anuke.arc.*;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.type.StatusEffect;
import static io.anuke.mindustry.Vars.waveTeam;
public class StatusEffects implements ContentList{
public static StatusEffect none, burning, freezing, wet, melting, tarred, overdrive, shielded, shocked, corroded, boss;
@@ -37,8 +41,14 @@ public class StatusEffects implements ContentList{
speedMultiplier = 0.9f;
effect = Fx.wet;
trans(() -> shocked, ((unit, time, newTime, result) -> unit.damage(15f)));
opposite(() -> burning, () -> shocked);
trans(() -> shocked, ((unit, time, newTime, result) -> {
unit.damage(20f);
if(unit.getTeam() == waveTeam){
Events.fire(Trigger.shock);
}
result.set(this, time);
}));
opposite(() -> burning);
}};
melting = new StatusEffect(){{

View File

@@ -8,10 +8,13 @@ import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.content.Blocks.*;
public class TechTree implements ContentList{
public static Array<TechNode> all;
public static TechNode root;
@Override
public void load(){
all = new Array<>();
root = node(coreShard, () -> {
node(conveyor, () -> {
@@ -326,6 +329,7 @@ public class TechTree implements ContentList{
context = this;
children.run();
context = last;
all.add(this);
}
}
}

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.content;
import io.anuke.arc.collection.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.type.base.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;

View File

@@ -2,7 +2,6 @@ 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.*;
import io.anuke.arc.input.*;
@@ -18,7 +17,6 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*;
@@ -30,6 +28,7 @@ import java.util.*;
import static io.anuke.arc.Core.*;
import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.Vars.net;
/**
* Control module.
@@ -48,6 +47,10 @@ public class Control implements ApplicationListener, Loadable{
private boolean wasPaused = false;
public Control(){
saves = new Saves();
tutorial = new Tutorial();
music = new MusicControl();
Events.on(StateChangeEvent.class, event -> {
if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){
Time.runTask(5f, platform::updateRPC);
@@ -64,7 +67,7 @@ public class Control implements ApplicationListener, Loadable{
Events.on(WorldLoadEvent.class, event -> {
Core.app.post(() -> Core.app.post(() -> {
if(Net.active() && player.getClosestCore() != null){
if(net.active() && player.getClosestCore() != null){
//set to closest core since that's where the player will probably respawn; prevents camera jumps
Core.camera.position.set(player.isDead() ? player.getClosestCore() : player);
}else{
@@ -96,7 +99,7 @@ public class Control implements ApplicationListener, Loadable{
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
//the restart dialog can show info for any number of scenarios
Call.onGameOver(event.winner);
if(state.rules.zone != null && !Net.client()){
if(state.rules.zone != null && !net.client()){
//remove zone save on game over
if(saves.getZoneSlot() != null && !state.rules.tutorial){
saves.getZoneSlot().delete();
@@ -106,12 +109,12 @@ public class Control implements ApplicationListener, Loadable{
//autohost for pvp maps
Events.on(WorldLoadEvent.class, event -> {
if(state.rules.pvp && !Net.active()){
if(state.rules.pvp && !net.active()){
try{
Net.host(port);
net.host(port);
player.isAdmin = true;
}catch(IOException e){
ui.showError(Core.bundle.format("server.error", Strings.parseException(e, true)));
ui.showException("$server.error", e);
Core.app.post(() -> state.set(State.menu));
}
}
@@ -148,14 +151,24 @@ public class Control implements ApplicationListener, Loadable{
Events.on(ZoneConfigureCompleteEvent.class, e -> {
ui.hudfrag.showToast(Core.bundle.format("zone.config.complete", e.zone.configureWave));
});
Events.on(Trigger.newGame, () -> {
TileEntity core = player.getClosestCore();
if(core == null) return;
app.post(() -> ui.hudfrag.showLand());
renderer.zoomIn(Fx.coreLand.lifetime);
app.post(() -> Effects.effect(Fx.coreLand, core.x, core.y, 0, core.block));
Time.run(Fx.coreLand.lifetime, () -> {
Effects.effect(Fx.launch, core);
Effects.shake(5f, 5f, core);
});
});
}
@Override
public void loadAsync(){
saves = new Saves();
tutorial = new Tutorial();
music = new MusicControl();
Draw.scl = 1f / Core.atlas.find("scale_marker").getWidth();
Core.input.setCatch(KeyCode.BACK, true);
@@ -174,35 +187,6 @@ public class Control implements ApplicationListener, Loadable{
saves.load();
}
//checks for existing 3.5 app data, android only
public void checkClassicData(){
try{
if(files.local("mindustry-maps").exists() || files.local("mindustry-saves").exists()){
settings.getBoolOnce("classic-backup-check", () -> {
app.post(() -> app.post(() -> ui.showConfirm("$classic.export", "$classic.export.text", () -> {
try{
platform.requestExternalPerms(() -> {
FileHandle external = files.external("MindustryClassic");
if(files.local("mindustry-maps").exists()){
files.local("mindustry-maps").copyTo(external);
}
if(files.local("mindustry-saves").exists()){
files.local("mindustry-saves").copyTo(external);
}
});
}catch(Exception e){
e.printStackTrace();
ui.showError(Strings.parseException(e, true));
}
})));
});
}
}catch(Throwable t){
t.printStackTrace();
}
}
void createPlayer(){
player = new Player();
player.name = Core.settings.getString("name");
@@ -220,9 +204,18 @@ public class Control implements ApplicationListener, Loadable{
player.add();
}
Events.on(ClientLoadEvent.class, e -> {
Core.input.addProcessor(input);
});
Events.on(ClientLoadEvent.class, e -> input.add());
}
public void setInput(InputHandler newInput){
Block block = input.block;
boolean added = Core.input.getInputProcessors().contains(input);
input.remove();
this.input = newInput;
newInput.block = block;
if(added){
newInput.add();
}
}
public void playMap(Map map, Rules rules){
@@ -230,17 +223,20 @@ public class Control implements ApplicationListener, Loadable{
logic.reset();
world.loadMap(map, rules);
state.rules = rules;
state.rules.zone = null;
state.rules.editor = false;
logic.play();
if(settings.getBool("savecreate") && !world.isInvalidMap()){
control.saves.addSave(map.name() + " " + new SimpleDateFormat("MMM dd h:mm", Locale.getDefault()).format(new Date()));
}
Events.fire(Trigger.newGame);
});
}
public void playZone(Zone zone){
ui.loadAnd(() -> {
logic.reset();
Net.reset();
net.reset();
world.loadGenerator(zone.generator);
zone.rules.accept(state.rules);
state.rules.zone = zone;
@@ -252,6 +248,7 @@ public class Control implements ApplicationListener, Loadable{
state.set(State.playing);
control.saves.zoneSave();
logic.play();
Events.fire(Trigger.newGame);
});
}
@@ -259,7 +256,7 @@ public class Control implements ApplicationListener, Loadable{
Zone zone = Zones.groundZero;
ui.loadAnd(() -> {
logic.reset();
Net.reset();
net.reset();
world.beginMapLoad();
@@ -304,6 +301,7 @@ public class Control implements ApplicationListener, Loadable{
state.rules.waveSpacing = 60f * 30;
state.rules.buildCostMultiplier = 0.3f;
state.rules.tutorial = true;
Events.fire(Trigger.newGame);
});
}
@@ -314,7 +312,7 @@ public class Control implements ApplicationListener, Loadable{
@Override
public void dispose(){
content.dispose();
Net.dispose();
net.dispose();
Musics.dispose();
Sounds.dispose();
ui.editor.dispose();
@@ -379,17 +377,20 @@ public class Control implements ApplicationListener, Loadable{
if(android){
Sounds.empty.loop(0f, 1f, 0f);
checkClassicData();
}
}
@Override
public void update(){
//TODO find out why this happens on Android
if(assets == null) return;
saves.update();
//update and load any requested assets
assets.update();
input.updateController();
input.updateState();
//autosave global data if it's modified
data.checkSave();
@@ -414,7 +415,7 @@ public class Control implements ApplicationListener, Loadable{
if(world.isZone()){
for(Tile tile : state.teams.get(player.getTeam()).cores){
for(Item item : content.items()){
if(tile.entity.items.has(item)){
if(tile.entity != null && tile.entity.items.has(item)){
data.unlockContent(item);
}
}

View File

@@ -1,14 +1,12 @@
package io.anuke.mindustry.core;
import io.anuke.arc.Events;
import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.entities.type.base.BaseDrone;
import io.anuke.mindustry.game.EventType.StateChangeEvent;
import io.anuke.arc.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.base.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.net.Net;
import static io.anuke.mindustry.Vars.unitGroups;
import static io.anuke.mindustry.Vars.waveTeam;
import static io.anuke.mindustry.Vars.*;
public class GameState{
/** Current wave number, can be anything in non-wave modes. */
@@ -29,7 +27,7 @@ public class GameState{
private State state = State.menu;
public int enemies(){
return Net.client() ? enemies : unitGroups[waveTeam.ordinal()].count(b -> !(b instanceof BaseDrone));
return net.client() ? enemies : unitGroups[waveTeam.ordinal()].count(b -> !(b instanceof BaseDrone));
}
public BaseUnit boss(){
@@ -46,7 +44,7 @@ public class GameState{
}
public boolean isPaused(){
return (is(State.paused) && !Net.active()) || (gameOver && !Net.active());
return (is(State.paused) && !net.active()) || (gameOver && !net.active());
}
public boolean is(State astate){

View File

@@ -1,28 +1,21 @@
package io.anuke.mindustry.core;
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.ObjectSet.ObjectSetIterator;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.ObjectSet.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Teams.TeamData;
import io.anuke.mindustry.gen.BrokenBlock;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BuildBlock;
import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.BuildBlock.*;
import static io.anuke.mindustry.Vars.*;
@@ -164,6 +157,7 @@ public class Logic implements ApplicationListener{
}
state.launched = true;
state.gameOver = true;
Events.fire(new LaunchEvent());
//manually fire game over event now
Events.fire(new GameOverEvent(defaultTeam));
});
@@ -190,7 +184,7 @@ public class Logic implements ApplicationListener{
}
}
if(!Net.client() && state.wavetime <= 0 && state.rules.waves){
if(!net.client() && state.wavetime <= 0 && state.rules.waves){
runWave();
}
@@ -233,11 +227,9 @@ public class Logic implements ApplicationListener{
collisions.collideGroups(bulletGroup, playerGroup);
}
pathfinder.update();
}
if(!Net.client() && !world.isInvalidMap() && !state.isEditor()){
if(!net.client() && !world.isInvalidMap() && !state.isEditor()){
checkGameOver();
}
}

View File

@@ -1,37 +1,33 @@
package io.anuke.mindustry.core;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.ApplicationListener;
import io.anuke.arc.Core;
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.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.math.*;
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.EntityGroup;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.TypeID;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.gen.RemoteReadClient;
import io.anuke.mindustry.net.Administration.TraceInfo;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.modules.ItemModule;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.modules.*;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.zip.InflaterInputStream;
import java.io.*;
import java.util.zip.*;
import static io.anuke.mindustry.Vars.*;
@@ -40,6 +36,7 @@ public class NetClient implements ApplicationListener{
private final static float playerSyncTime = 2;
public final static float viewScale = 2f;
private long ping;
private Interval timer = new Interval(5);
/** Whether the client is currently connecting. */
private boolean connecting = false;
@@ -60,7 +57,7 @@ public class NetClient implements ApplicationListener{
public NetClient(){
Net.handleClient(Connect.class, packet -> {
net.handleClient(Connect.class, packet -> {
Log.info("Connecting to server: {0}", packet.addressTCP);
player.isAdmin = false;
@@ -74,7 +71,7 @@ public class NetClient implements ApplicationListener{
ui.loadfrag.hide();
connecting = false;
quiet = true;
Net.disconnect();
net.disconnect();
});
ConnectPacket c = new ConnectPacket();
@@ -86,16 +83,16 @@ public class NetClient implements ApplicationListener{
c.uuid = platform.getUUID();
if(c.uuid == null){
ui.showError("$invalidid");
ui.showErrorMessage("$invalidid");
ui.loadfrag.hide();
disconnectQuietly();
return;
}
Net.send(c, SendMode.tcp);
net.send(c, SendMode.tcp);
});
Net.handleClient(Disconnect.class, packet -> {
net.handleClient(Disconnect.class, packet -> {
if(quietReset) return;
connecting = false;
@@ -116,18 +113,18 @@ public class NetClient implements ApplicationListener{
ui.showSmall("$disconnect", "$disconnect.error");
}
}else{
ui.showError("$disconnect");
ui.showErrorMessage("$disconnect");
}
});
Net.handleClient(WorldStream.class, data -> {
net.handleClient(WorldStream.class, data -> {
Log.info("Recieved world data: {0} bytes.", data.stream.available());
NetworkIO.loadWorld(new InflaterInputStream(data.stream));
finishConnecting();
});
Net.handleClient(InvokePacket.class, packet -> {
net.handleClient(InvokePacket.class, packet -> {
packet.writeBuffer.position(0);
RemoteReadClient.readPacket(packet.writeBuffer, packet.type);
});
@@ -190,6 +187,8 @@ public class NetClient implements ApplicationListener{
player.sendMessage(text);
}
}
Events.fire(new PlayerChatEvent(player, message));
}
public static String colorizeName(int id, String name){
@@ -198,6 +197,16 @@ public class NetClient implements ApplicationListener{
return "[#" + player.color.toString().toUpperCase() + "]" + name;
}
@Remote(targets = Loc.client)
public static void onPing(Player player, long time){
Call.onPingResponse(player.con, time);
}
@Remote(variants = Variant.one)
public static void onPingResponse(long time){
netClient.ping = Time.timeSinceMillis(time);
}
@Remote(variants = Variant.one)
public static void onTraceInfo(Player player, TraceInfo info){
if(player != null){
@@ -233,7 +242,7 @@ public class NetClient implements ApplicationListener{
logic.reset();
ui.chatfrag.clearMessages();
Net.setClientLoaded(false);
net.setClientLoaded(false);
ui.loadfrag.show("$connecting.data");
@@ -241,7 +250,7 @@ public class NetClient implements ApplicationListener{
ui.loadfrag.hide();
netClient.connecting = false;
netClient.quiet = true;
Net.disconnect();
net.disconnect();
});
}
@@ -259,7 +268,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onEntitySnapshot(byte groupID, short amount, short dataLen, byte[] data){
try{
netClient.byteStream.setBytes(Net.decompressSnapshot(data, dataLen));
netClient.byteStream.setBytes(net.decompressSnapshot(data, dataLen));
DataInputStream input = netClient.dataStream;
EntityGroup group = entities.get(groupID);
@@ -315,7 +324,7 @@ public class NetClient implements ApplicationListener{
state.wave = wave;
state.enemies = enemies;
netClient.byteStream.setBytes(Net.decompressSnapshot(coreData, coreDataLen));
netClient.byteStream.setBytes(net.decompressSnapshot(coreData, coreDataLen));
DataInputStream input = netClient.dataStream;
byte cores = input.readByte();
@@ -337,20 +346,20 @@ public class NetClient implements ApplicationListener{
@Override
public void update(){
if(!Net.client()) return;
if(!net.client()) return;
if(!state.is(State.menu)){
if(!connecting) sync();
}else if(!connecting){
Net.disconnect();
net.disconnect();
}else{ //...must be connecting
timeoutTime += Time.delta();
if(timeoutTime > dataTimeout){
Log.err("Failed to load data!");
ui.loadfrag.hide();
quiet = true;
ui.showError("$disconnect.data");
Net.disconnect();
ui.showErrorMessage("$disconnect.data");
net.disconnect();
timeoutTime = 0f;
}
}
@@ -360,18 +369,22 @@ public class NetClient implements ApplicationListener{
return connecting;
}
public int getPing(){
return (int)ping;
}
private void finishConnecting(){
state.set(State.playing);
connecting = false;
ui.join.hide();
Net.setClientLoaded(true);
net.setClientLoaded(true);
Core.app.post(Call::connectConfirm);
Time.runTask(40f, platform::updateRPC);
Core.app.post(() -> ui.loadfrag.hide());
}
private void reset(){
Net.setClientLoaded(false);
net.setClientLoaded(false);
removed.clear();
timeoutTime = 0f;
connecting = true;
@@ -390,13 +403,13 @@ public class NetClient implements ApplicationListener{
/** Disconnects, resetting state to the menu. */
public void disconnectQuietly(){
quiet = true;
Net.disconnect();
net.disconnect();
}
/** Disconnects, causing no further changes or reset.*/
public void disconnectNoReset(){
quiet = quietReset = true;
Net.disconnect();
net.disconnect();
}
/** When set, any disconnects will be ignored and no dialogs will be shown. */
@@ -435,7 +448,7 @@ public class NetClient implements ApplicationListener{
}
if(timer.get(1, 60)){
Net.updatePing();
Call.onPing(Time.millis());
}
}

View File

@@ -19,7 +19,6 @@ import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.world.*;
@@ -41,8 +40,6 @@ public class NetServer implements ApplicationListener{
public final Administration admins = new Administration();
public final CommandHandler clientCommands = new CommandHandler("/");
/** Maps connection IDs to players. */
private IntMap<Player> connections = new IntMap<>();
private boolean closing = false;
private ByteBuffer writeBuffer = ByteBuffer.allocate(127);
@@ -54,57 +51,51 @@ public class NetServer implements ApplicationListener{
private DataOutputStream dataStream = new DataOutputStream(syncStream);
public NetServer(){
Events.on(WorldLoadEvent.class, event -> {
if(!headless){
connections.clear();
}
});
Net.handleServer(Connect.class, (id, connect) -> {
net.handleServer(Connect.class, (con, connect) -> {
if(admins.isIPBanned(connect.addressTCP)){
kick(id, KickReason.banned);
con.kick(KickReason.banned);
}
});
Net.handleServer(Disconnect.class, (id, packet) -> {
Player player = connections.get(id);
if(player != null){
onDisconnect(player, packet.reason);
net.handleServer(Disconnect.class, (con, packet) -> {
if(con.player != null){
onDisconnect(con.player, packet.reason);
}
connections.remove(id);
});
Net.handleServer(ConnectPacket.class, (id, packet) -> {
net.handleServer(ConnectPacket.class, (con, packet) -> {
String uuid = packet.uuid;
NetConnection connection = Net.getConnection(id);
if(admins.isIPBanned(con.address)) return;
if(connection == null ||
admins.isIPBanned(connection.address)) return;
if(connection.hasBegunConnecting){
kick(id, KickReason.idInUse);
if(con.hasBegunConnecting){
con.kick(KickReason.idInUse);
return;
}
connection.hasBegunConnecting = true;
PlayerInfo info = admins.getInfo(uuid);
connection.mobile = packet.mobile;
con.hasBegunConnecting = true;
con.mobile = packet.mobile;
if(admins.isIDBanned(uuid)){
kick(id, KickReason.banned);
con.kick(KickReason.banned);
return;
}
if(Time.millis() - info.lastKicked < kickDuration){
kick(id, KickReason.recentKick);
con.kick(KickReason.recentKick);
return;
}
if(admins.isIDBanned(uuid)){
kick(id, KickReason.banned);
con.kick(KickReason.banned);
return;
}
if(admins.getPlayerLimit() > 0 && playerGroup.size() >= admins.getPlayerLimit()){
con.kick(KickReason.playerLimit);
return;
}
@@ -113,14 +104,14 @@ public class NetServer implements ApplicationListener{
info.lastName = packet.name;
info.id = packet.uuid;
admins.save();
Call.onInfoMessage(id, "You are not whitelisted here.");
Call.onInfoMessage(con, "You are not whitelisted here.");
Log.info("&lcDo &lywhitelist-add {0}&lc to whitelist the player &lb'{1}'", packet.uuid, packet.name);
kick(id, KickReason.whitelist);
con.kick(KickReason.whitelist);
return;
}
if(packet.versionType == null || ((packet.version == -1 || !packet.versionType.equals(Version.type)) && Version.build != -1 && !admins.allowsCustomClients())){
kick(id, !Version.type.equals(packet.versionType) ? KickReason.typeMismatch : KickReason.customClient);
con.kick(!Version.type.equals(packet.versionType) ? KickReason.typeMismatch : KickReason.customClient);
return;
}
@@ -129,12 +120,12 @@ public class NetServer implements ApplicationListener{
if(preventDuplicates){
for(Player player : playerGroup.all()){
if(player.name.trim().equalsIgnoreCase(packet.name.trim())){
kick(id, KickReason.nameInUse);
con.kick(KickReason.nameInUse);
return;
}
if(player.uuid.equals(packet.uuid) || player.usid.equals(packet.usid)){
kick(id, KickReason.idInUse);
con.kick(KickReason.idInUse);
return;
}
}
@@ -143,28 +134,26 @@ public class NetServer implements ApplicationListener{
packet.name = fixName(packet.name);
if(packet.name.trim().length() <= 0){
kick(id, KickReason.nameEmpty);
con.kick(KickReason.nameEmpty);
return;
}
Log.debug("Recieved connect packet for player '{0}' / UUID {1} / IP {2}", packet.name, uuid, connection.address);
String ip = Net.getConnection(id).address;
String ip = con.address;
admins.updatePlayerJoined(uuid, ip, packet.name);
if(packet.version != Version.build && Version.build != -1 && packet.version != -1){
kick(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
con.kick(packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
return;
}
if(packet.version == -1){
connection.modclient = true;
con.modclient = true;
}
Player player = new Player();
player.isAdmin = admins.isAdmin(uuid, packet.usid);
player.con = Net.getConnection(id);
player.con = con;
player.usid = packet.usid;
player.name = packet.name;
player.uuid = uuid;
@@ -179,29 +168,28 @@ public class NetServer implements ApplicationListener{
player.write(outputBuffer);
}catch(Throwable t){
t.printStackTrace();
kick(id, KickReason.nameEmpty);
con.kick(KickReason.nameEmpty);
return;
}
con.player = player;
//playing in pvp mode automatically assigns players to teams
if(state.rules.pvp){
player.setTeam(assignTeam(player, playerGroup.all()));
Log.info("Auto-assigned player {0} to team {1}.", player.name, player.getTeam());
}
connections.put(id, player);
sendWorldData(player, id);
sendWorldData(player);
platform.updateRPC();
Events.fire(new PlayerJoin(player));
Events.fire(new PlayerConnect(player));
});
Net.handleServer(InvokePacket.class, (id, packet) -> {
Player player = connections.get(id);
if(player == null) return;
RemoteReadServer.readPacket(packet.writeBuffer, packet.type, player);
net.handleServer(InvokePacket.class, (con, packet) -> {
if(con.player == null) return;
RemoteReadServer.readPacket(packet.writeBuffer, packet.type, con.player);
});
registerCommands();
@@ -273,7 +261,7 @@ public class NetServer implements ApplicationListener{
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);
target.con.kick(KickReason.vote);
map[0] = null;
task.cancel();
return true;
@@ -305,14 +293,14 @@ public class NetServer implements ApplicationListener{
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");
builder.append("[lightgray] ").append(p.name).append("[accent] (#").append(p.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);
found = playerGroup.find(p -> p.id == id);
}else{
found = playerGroup.find(p -> p.name.equalsIgnoreCase(args[0]));
}
@@ -368,8 +356,8 @@ public class NetServer implements ApplicationListener{
if(player.isLocal){
player.sendMessage("[scarlet]Re-synchronizing as the host is pointless.");
}else{
Call.onWorldDataBegin(player.con.id);
netServer.sendWorldData(player, player.con.id);
Call.onWorldDataBegin(player.con);
netServer.sendWorldData(player);
}
});
}
@@ -394,13 +382,13 @@ public class NetServer implements ApplicationListener{
});
}
public void sendWorldData(Player player, int clientID){
public void sendWorldData(Player player){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
DeflaterOutputStream def = new FastDeflaterOutputStream(stream);
NetworkIO.writeWorld(player, def);
WorldStream data = new WorldStream();
data.stream = new ByteArrayInputStream(stream.toByteArray());
Net.sendStream(clientID, data);
player.con.sendStream(data);
Log.debug("Packed {0} compressed bytes of world data.", stream.size());
}
@@ -418,7 +406,6 @@ public class NetServer implements ApplicationListener{
Call.onPlayerDisconnect(player.id);
}
player.remove();
netServer.connections.remove(player.con.id);
Log.info("&lm[{1}] &lc{0} has disconnected. &lg&fi({2})", player.name, player.uuid, reason);
}
@@ -500,7 +487,7 @@ public class NetServer implements ApplicationListener{
newx = x;
newy = y;
}else if(Mathf.dst(x, y, newx, newy) > correctDist){
Call.onPositionSet(player.con.id, newx, newy); //teleport and correct position when necessary
Call.onPositionSet(player.con, newx, newy); //teleport and correct position when necessary
}
//reset player to previous synced position so it gets interpolated
@@ -535,15 +522,15 @@ public class NetServer implements ApplicationListener{
state.wavetime = 0f;
}else if(action == AdminAction.ban){
netServer.admins.banPlayerIP(other.con.address);
netServer.kick(other.con.id, KickReason.banned);
other.con.kick(KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name, other.name);
}else if(action == AdminAction.kick){
netServer.kick(other.con.id, KickReason.kick);
other.con.kick(KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
}else if(action == AdminAction.trace){
TraceInfo info = new TraceInfo(other.con.address, other.uuid, other.con.modclient, other.con.mobile);
if(player.con != null){
Call.onTraceInfo(player.con.id, other, info);
Call.onTraceInfo(player.con, other, info);
}else{
NetClient.onTraceInfo(other, info);
}
@@ -559,6 +546,8 @@ public class NetServer implements ApplicationListener{
player.con.hasConnected = true;
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
Log.info("&lm[{1}] &y{0} has connected. ", player.name, player.uuid);
Events.fire(new PlayerJoin(player));
}
public boolean isWaitingForPlayers(){
@@ -576,51 +565,27 @@ public class NetServer implements ApplicationListener{
public void update(){
if(!headless && !closing && Net.server() && state.is(State.menu)){
if(!headless && !closing && net.server() && state.is(State.menu)){
closing = true;
ui.loadfrag.show("$server.closing");
Time.runTask(5f, () -> {
Net.closeServer();
net.closeServer();
ui.loadfrag.hide();
closing = false;
});
}
if(!state.is(State.menu) && Net.server()){
if(!state.is(State.menu) && net.server()){
sync();
}
}
public void kickAll(KickReason reason){
for(NetConnection con : Net.getConnections()){
kick(con.id, reason);
for(NetConnection con : net.getConnections()){
con.kick(reason);
}
}
public void kick(int connection, KickReason reason){
NetConnection con = Net.getConnection(connection);
if(con == null){
Log.err("Cannot kick unknown player!");
return;
}else{
Log.info("Kicking connection #{0} / IP: {1}. Reason: {2}", connection, con.address, reason.name());
}
Player player = connections.get(con.id);
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 = Math.max(Time.millis(), info.lastKicked);
}
Call.onKick(connection, reason);
Time.runTask(2f, con::close);
admins.save();
}
public void writeSnapshot(Player player) throws IOException{
syncStream.reset();
ObjectSet<Tile> cores = state.teams.get(player.getTeam()).cores;
@@ -636,7 +601,7 @@ public class NetServer implements ApplicationListener{
byte[] stateBytes = syncStream.toByteArray();
//write basic state data.
Call.onStateSnapshot(player.con.id, state.wavetime, state.wave, state.enemies(), (short)stateBytes.length, Net.compressSnapshot(stateBytes));
Call.onStateSnapshot(player.con, state.wavetime, state.wave, state.enemies(), (short)stateBytes.length, net.compressSnapshot(stateBytes));
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);
@@ -667,7 +632,7 @@ public class NetServer implements ApplicationListener{
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
byte[] syncBytes = syncStream.toByteArray();
Call.onEntitySnapshot(player.con.id, (byte)group.getID(), (short)sent, (short)syncBytes.length, Net.compressSnapshot(syncBytes));
Call.onEntitySnapshot(player.con, (byte)group.getID(), (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
sent = 0;
syncStream.reset();
}
@@ -677,7 +642,7 @@ public class NetServer implements ApplicationListener{
dataStream.close();
byte[] syncBytes = syncStream.toByteArray();
Call.onEntitySnapshot(player.con.id, (byte)group.getID(), (short)sent, (short)syncBytes.length, Net.compressSnapshot(syncBytes));
Call.onEntitySnapshot(player.con, (byte)group.getID(), (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
}
}
}
@@ -739,16 +704,10 @@ public class NetServer implements ApplicationListener{
//iterate through each player
for(int i = 0; i < playerGroup.size(); i++){
Player player = playerGroup.all().get(i);
if(player.isLocal) continue;
if(player.isLocal || player.con == null) continue;
NetConnection connection = player.con;
if(connection == null || !connection.isConnected() || !connections.containsKey(connection.id)){
//player disconnected, call d/c event
onDisconnect(player, "disappeared");
return;
}
if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue;
writeSnapshot(player);

View File

@@ -1,18 +1,47 @@
package io.anuke.mindustry.core;
import io.anuke.arc.Core;
import io.anuke.arc.Input.TextInput;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.math.RandomXS128;
import io.anuke.arc.scene.ui.TextField;
import io.anuke.arc.util.serialization.Base64Coder;
import io.anuke.arc.*;
import io.anuke.arc.Input.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.mobile;
public interface Platform{
/** Steam: Update lobby visibility.*/
default void updateLobby(){}
/** Steam: Show multiplayer friend invite dialog.*/
default void inviteFriends(){}
/** Steam: Share a map on the workshop.*/
default void publishMap(Map map){}
/** Steam: Return external workshop maps to be loaded.*/
default Array<FileHandle> getExternalMaps(){
return Array.with();
}
/** Steam: View a map listing on the workshop.*/
default void viewMapListing(Map map){}
/** Steam: Open workshop for maps.*/
default void openWorkshop(){}
/** Get the networking implementation.*/
default NetProvider getNet(){
return new ArcNetImpl();
}
/** Add a text input dialog that should show up after the field is tapped. */
default void addDialog(TextField field){
addDialog(field, 16);
@@ -36,11 +65,6 @@ public interface Platform{
});
}
/** Request external read/write perms. Run callback when complete.*/
default void requestExternalPerms(Runnable callback){
callback.run();
}
/** Update discord RPC. */
default void updateRPC(){
}
@@ -70,24 +94,29 @@ public interface Platform{
/**
* Show a file chooser.
* @param text File chooser title text
* @param content Description of the type of files to be loaded
* @param cons Selection listener
* @param open Whether to open or save files
* @param filetype File extension to filter
* @param extension File extension to filter
*/
default void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, Predicate<String> filetype){
default void showFileChooser(boolean open, String extension, Consumer<FileHandle> cons){
new FileChooser(open ? "$open" : "$save", file -> file.extension().toLowerCase().equals(extension), open, file -> {
if(!open){
cons.accept(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{
cons.accept(file);
}
}).show();
}
/** Hide the app. Android only. */
default void hide(){
}
/** Forces the app into landscape mode. Currently Android only. */
/** Forces the app into landscape mode.*/
default void beginForceLandscape(){
}
/** Stops forcing the app into landscape orientation. Currently Android only. */
/** Stops forcing the app into landscape orientation.*/
default void endForceLandscape(){
}
}

View File

@@ -16,12 +16,14 @@ import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.effect.GroundEffectEntity.*;
import io.anuke.mindustry.entities.impl.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.EffectEntity;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import static io.anuke.arc.Core.*;
@@ -36,8 +38,10 @@ public class Renderer implements ApplicationListener{
public FrameBuffer shieldBuffer = new FrameBuffer(2, 2);
private Bloom bloom;
private Color clearColor;
private float targetscale = UnitScl.dp.scl(4);
private float targetscale = Scl.scl(4);
private float camerascale = targetscale;
private float landscale = 0f, landTime;
private float minZoomScl = Scl.scl(0.01f);
private Rectangle rect = new Rectangle(), rect2 = new Rectangle();
private float shakeIntensity, shaketime;
@@ -99,15 +103,22 @@ public class Renderer implements ApplicationListener{
@Override
public void update(){
//TODO hack, find source of this bug
Color.WHITE.set(1f, 1f, 1f, 1f);
Color.white.set(1f, 1f, 1f, 1f);
camerascale = Mathf.lerpDelta(camerascale, targetscale, 0.1f);
if(landTime > 0){
landTime -= Time.delta();
landscale = Interpolation.pow5In.apply(minZoomScl, Scl.scl(4f), 1f - landTime / Fx.coreLand.lifetime);
camerascale = landscale;
}
camera.width = graphics.getWidth() / camerascale;
camera.height = graphics.getHeight() / camerascale;
if(state.is(State.menu)){
graphics.clear(Color.BLACK);
landTime = 0f;
graphics.clear(Color.black);
}else{
Vector2 position = Tmp.v3.set(player);
@@ -118,7 +129,7 @@ public class Renderer implements ApplicationListener{
}else{
camera.position.lerpDelta(position, 0.08f);
}
}else if(!mobile || settings.getBool("keyboard")){
}else if(control.input instanceof DesktopInput){
camera.position.lerpDelta(position, 0.08f);
}
@@ -131,6 +142,10 @@ public class Renderer implements ApplicationListener{
}
}
public float landScale(){
return landTime > 0 ? landscale : 1f;
}
@Override
public void dispose(){
minimap.dispose();
@@ -162,7 +177,7 @@ public class Renderer implements ApplicationListener{
e.printStackTrace();
settings.put("bloom", false);
settings.save();
ui.showError("$error.bloom");
ui.showErrorMessage("$error.bloom");
}
}
@@ -257,13 +272,13 @@ public class Renderer implements ApplicationListener{
}
overlays.drawBottom();
playerGroup.draw(p -> true, Player::drawBuildRequests);
playerGroup.draw(p -> p.isLocal, Player::drawBuildRequests);
if(shieldGroup.countInBounds() > 0){
if(settings.getBool("animatedshields") && Shaders.shield != null){
Draw.flush();
shieldBuffer.begin();
graphics.clear(Color.CLEAR);
graphics.clear(Color.clear);
shieldGroup.draw();
shieldGroup.draw(shield -> true, ShieldEntity::drawOver);
Draw.flush();
@@ -282,10 +297,37 @@ public class Renderer implements ApplicationListener{
playerGroup.draw(p -> !p.isDead() && !p.isLocal, Player::drawName);
drawLanding();
Draw.color();
Draw.flush();
}
private void drawLanding(){
if(landTime > 0 && player.getClosestCore() != null){
float fract = landTime / Fx.coreLand.lifetime;
TileEntity entity = player.getClosestCore();
TextureRegion reg = entity.block.icon(Block.Icon.full);
float scl = Scl.scl(4f) / camerascale;
float s = reg.getWidth() * Draw.scl * scl * 4f * fract;
Draw.color(Pal.lightTrail);
Draw.rect("circle-shadow", entity.x, entity.y, s, s);
Angles.randLenVectors(1, (1f- fract), 100, 1000f * scl * (1f-fract), (x, y, fin, fout) -> {
Lines.stroke(scl * fin);
Lines.lineAngle(entity.x + x, entity.y + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
});
Draw.color();
Draw.mixcol(Color.white, fract);
Draw.rect(reg, entity.x, entity.y, reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
Draw.reset();
}
}
private void drawGroundShadows(){
Draw.color(0, 0, 0, 0.4f);
float rad = 1.6f;
@@ -348,7 +390,7 @@ public class Renderer implements ApplicationListener{
}
public void clampScale(){
float s = UnitScl.dp.scl(1f);
float s = Scl.scl(1f);
targetscale = Mathf.clamp(targetscale, s * 1.5f, Math.round(s * 6));
}
@@ -361,6 +403,11 @@ public class Renderer implements ApplicationListener{
clampScale();
}
public void zoomIn(float duration){
landscale = minZoomScl;
landTime = duration;
}
public void takeMapScreenshot(){
drawGroundShadows();

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.Graphics.*;
import io.anuke.arc.Graphics.Cursor.*;
import io.anuke.arc.Input.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.assets.loaders.*;
import io.anuke.arc.assets.loaders.resolvers.*;
@@ -13,14 +14,13 @@ 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.Texture.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.g2d.TextureAtlas.*;
import io.anuke.arc.input.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.*;
import io.anuke.arc.scene.actions.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.TextField.*;
import io.anuke.arc.scene.ui.Tooltip.*;
@@ -31,6 +31,7 @@ import io.anuke.mindustry.editor.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.ui.fragments.*;
@@ -38,8 +39,6 @@ import static io.anuke.arc.scene.actions.Actions.*;
import static io.anuke.mindustry.Vars.*;
public class UI implements ApplicationListener, Loadable{
private Skin skin;
public MenuFragment menufrag;
public HudFragment hudfrag;
public ChatFragment chatfrag;
@@ -73,7 +72,6 @@ public class UI implements ApplicationListener, Loadable{
public Cursor drillCursor, unloadCursor;
public UI(){
skin = new Skin();
setupFonts();
}
@@ -84,22 +82,19 @@ public class UI implements ApplicationListener, Loadable{
@Override
public void loadSync(){
//TODO type-safe skin files
skin.addRegions(Core.atlas);
loadExtraStyle(skin);
skin.add("outline", Core.assets.get("outline"));
skin.getFont("outline").getData().markupEnabled = true;
skin.getFont("default").getData().markupEnabled = true;
skin.getFont("default").setOwnsTexture(false);
skin.load(Core.files.internal("sprites/uiskin.json"));
Fonts.outline.getData().markupEnabled = true;
Fonts.def.getData().markupEnabled = true;
Fonts.def.setOwnsTexture(false);
for(BitmapFont font : skin.getAll(BitmapFont.class).values()){
font.setUseIntegerPositions(true);
}
Core.scene = new Scene(skin);
Core.assets.getAll(BitmapFont.class, new Array<>()).each(font -> font.setUseIntegerPositions(true));
Core.scene = new Scene();
Core.input.addProcessor(Core.scene);
Tex.load();
Icon.load();
Styles.load();
Tex.loadStyles();
Dialog.setShowAction(() -> sequence(alpha(0f), fadeIn(0.1f)));
Dialog.setHideAction(() -> sequence(fadeOut(0.1f)));
@@ -107,13 +102,13 @@ public class UI implements ApplicationListener, Loadable{
Core.settings.setErrorHandler(e -> {
e.printStackTrace();
Core.app.post(() -> showError("Failed to access local storage.\nSettings will not be saved."));
Core.app.post(() -> showErrorMessage("Failed to access local storage.\nSettings will not be saved."));
});
ClickListener.clicked = () -> Sounds.press.play();
Colors.put("accent", Pal.accent);
Colors.put("highlight", Pal.accent.cpy().lerp(Color.WHITE, 0.3f));
Colors.put("highlight", Pal.accent.cpy().lerp(Color.white, 0.3f));
Colors.put("stat", Pal.stat);
loadExtraCursors();
}
@@ -140,39 +135,22 @@ public class UI implements ApplicationListener, Loadable{
@Override
public BitmapFont loadSync(AssetManager manager, String fileName, FileHandle file, FreeTypeFontLoaderParameter parameter){
if(fileName.equals("outline")){
parameter.fontParameters.borderWidth = UnitScl.dp.scl(2f);
parameter.fontParameters.borderWidth = Scl.scl(2f);
parameter.fontParameters.spaceX -= parameter.fontParameters.borderWidth;
}
parameter.fontParameters.magFilter = TextureFilter.Linear;
parameter.fontParameters.minFilter = TextureFilter.Linear;
parameter.fontParameters.size = fontParameter().size;
return super.loadSync(manager, fileName, file, parameter);
}
});
FreeTypeFontParameter param = new FreeTypeFontParameter(){{
borderColor = Color.DARK_GRAY;
borderColor = Color.darkGray;
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;
ScaledNinePatchDrawable copy = new ScaledNinePatchDrawable(new NinePatch(region, splits[0], splits[1], splits[2], splits[3])){
public float getLeftWidth(){ return 0; }
public float getRightWidth(){ return 0; }
public float getTopHeight(){ return 0; }
public float getBottomHeight(){ return 0; }
};
copy.setMinWidth(0);
copy.setMinHeight(0);
copy.setTopHeight(0);
copy.setRightWidth(0);
copy.setBottomHeight(0);
copy.setLeftWidth(0);
skin.add("flat-down", copy, Drawable.class);
Core.assets.load("outline", BitmapFont.class, new FreeTypeFontLoaderParameter("fonts/font.ttf", param)).loaded = t -> Fonts.outline = (BitmapFont)t;
}
void loadExtraCursors(){
@@ -185,14 +163,14 @@ public class UI implements ApplicationListener, Loadable{
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);
Core.assets.load("default", BitmapFont.class, new FreeTypeFontLoaderParameter(fontName, param)).loaded = f -> Fonts.def = (BitmapFont)f;
Core.assets.load("chat", BitmapFont.class, new FreeTypeFontLoaderParameter(fontName, param)).loaded = f -> Fonts.chat = (BitmapFont)f;
}
static FreeTypeFontParameter fontParameter(){
return new FreeTypeFontParameter(){{
size = (int)(UnitScl.dp.scl(18f));
shadowColor = Color.DARK_GRAY;
size = (int)(Scl.scl(18f));
shadowColor = Color.darkGray;
shadowOffsetY = 2;
incremental = true;
}};
@@ -290,28 +268,37 @@ public class UI implements ApplicationListener, Loadable{
});
}
public void showTextInput(String titleText, String text, int textLength, String def, TextFieldFilter filter, Consumer<String> confirmed){
new Dialog(titleText, "dialog"){{
cont.margin(30).add(text).padRight(6f);
TextField field = cont.addField(def, t -> {
}).size(170f, 50f).get();
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
platform.addDialog(field);
buttons.defaults().size(120, 54).pad(4);
buttons.addButton("$ok", () -> {
confirmed.accept(field.getText());
hide();
}).disabled(b -> field.getText().isEmpty());
buttons.addButton("$cancel", this::hide);
}}.show();
public void showTextInput(String titleText, String dtext, int textLength, String def, boolean inumeric, Consumer<String> confirmed){
if(mobile){
Core.input.getTextInput(new TextInput(){{
this.title = (titleText.startsWith("$") ? Core.bundle.get(titleText.substring(1)) : titleText);
this.text = def;
this.numeric = inumeric;
this.maxLength = textLength;
this.accepted = confirmed;
}});
}else{
new Dialog(titleText){{
cont.margin(30).add(dtext).padRight(6f);
TextFieldFilter filter = inumeric ? TextFieldFilter.digitsOnly : (f, c) -> true;
TextField field = cont.addField(def, t -> {}).size(170f, 50f).get();
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
buttons.defaults().size(120, 54).pad(4);
buttons.addButton("$ok", () -> {
confirmed.accept(field.getText());
hide();
}).disabled(b -> field.getText().isEmpty());
buttons.addButton("$cancel", this::hide);
}}.show();
}
}
public void showTextInput(String title, String text, String def, Consumer<String> confirmed){
showTextInput(title, text, 12, def, (field, c) -> true, confirmed);
showTextInput(title, text, 24, def, confirmed);
}
public void showTextInput(String title, String text, int textLength, String def, Consumer<String> confirmed){
showTextInput(title, text, textLength < 0 ? 12 : textLength, def, (field, c) -> true, confirmed);
public void showTextInput(String titleText, String text, int textLength, String def, Consumer<String> confirmed){
showTextInput(titleText, text, textLength, def, false, confirmed);
}
public void showInfoFade(String info){
@@ -323,48 +310,72 @@ public class UI implements ApplicationListener, Loadable{
}
public void showInfo(String info){
new Dialog("", "dialog"){{
new Dialog(""){{
getCell(cont).growX();
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center);
buttons.addButton("$ok", this::hide).size(90, 50).pad(4);
}}.show();
}
public void showError(String text){
new Dialog("", "dialog"){{
public void showErrorMessage(String text){
new Dialog(""){{
setFillParent(true);
cont.margin(15f);
cont.add("$error.title");
cont.row();
cont.margin(15).pane(t -> {
Label l = t.add(text).pad(14f).get();
l.setAlignment(Align.center, Align.left);
if(mobile){
t.getCell(l).wrap().width(400f);
}
});
buttons.addButton("$ok", this::hide).size(90, 50).pad(4);
cont.addImage().width(300f).pad(2).height(4f).color(Color.scarlet);
cont.row();
cont.add(text).pad(2f).growX().wrap().get().setAlignment(Align.center);
cont.row();
cont.addButton("$ok", this::hide).size(120, 50).pad(4);
}}.show();
}
public void showException(Throwable t){
showException("", t);
}
public void showException(String text, Throwable exc){
new Dialog(""){{
String message = Strings.getFinalMesage(exc);
setFillParent(true);
cont.margin(15);
cont.add("$error.title").colspan(2);
cont.row();
cont.addImage().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
cont.row();
cont.add((text.startsWith("$") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
cont.row();
Collapser col = new Collapser(base -> base.pane(t -> t.margin(14f).add(Strings.parseException(exc, true)).color(Color.lightGray).left()), true);
cont.addButton("$details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
cont.addButton("$ok", this::hide).size(100, 50).fillX().left();
cont.row();
cont.add(col).colspan(2).pad(2);
}}.show();
}
public void showText(String titleText, String text){
new Dialog(titleText, "dialog"){{
new Dialog(titleText){{
cont.margin(15).add(text).width(400f).wrap().get().setAlignment(Align.center, Align.center);
buttons.addButton("$ok", this::hide).size(90, 50).pad(4);
}}.show();
}
public void showInfoText(String titleText, String text){
new Dialog(titleText, "dialog"){{
new Dialog(titleText){{
cont.margin(15).add(text).width(400f).wrap().left().get().setAlignment(Align.left, Align.left);
buttons.addButton("$ok", this::hide).size(90, 50).pad(4);
}}.show();
}
public void showSmall(String titleText, String text){
new Dialog(titleText, "dialog"){{
new Dialog(titleText){{
cont.margin(10).add(text);
titleTable.row();
titleTable.addImage("whiteui").color(Pal.accent).height(3f).growX().pad(2f);
titleTable.addImage().color(Pal.accent).height(3f).growX().pad(2f);
buttons.addButton("$ok", this::hide).size(90, 50).pad(4);
}}.show();
}

View File

@@ -215,7 +215,7 @@ public class World{
}catch(Exception e){
Log.err(e);
if(!headless){
ui.showError("$map.invalid");
ui.showErrorMessage("$map.invalid");
Core.app.post(() -> state.set(State.menu));
invalidMap = true;
}
@@ -229,7 +229,7 @@ public class World{
if(!headless){
if(state.teams.get(defaultTeam).cores.size == 0 && !checkRules.pvp){
ui.showError("$map.nospawn");
ui.showErrorMessage("$map.nospawn");
invalidMap = true;
}else if(checkRules.pvp){ //pvp maps need two cores to be valid
int teams = 0;
@@ -240,12 +240,12 @@ public class World{
}
if(teams < 2){
invalidMap = true;
ui.showError("$map.nospawn.pvp");
ui.showErrorMessage("$map.nospawn.pvp");
}
}else if(checkRules.attackMode){ //attack maps need two cores to be valid
invalidMap = state.teams.get(waveTeam).cores.isEmpty();
if(invalidMap){
ui.showError("$map.nospawn.attack");
ui.showErrorMessage("$map.nospawn.attack");
}
}
}else{

View File

@@ -13,19 +13,19 @@ import io.anuke.arc.scene.actions.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.TextButton.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.*;
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.dialogs.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.Block.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.storage.*;
@@ -47,9 +47,9 @@ public class MapEditorDialog extends Dialog implements Disposable{
private Array<Block> blocksOut = new Array<>();
public MapEditorDialog(){
super("", "dialog");
super("");
background("dark");
background(Styles.black);
editor = new MapEditor();
view = new MapView(editor);
@@ -59,82 +59,75 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu = new FloatingDialog("$menu");
menu.addCloseButton();
float isize = iconsize;
float swidth = 180f;
menu.cont.table(t -> {
t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5);
t.addImageTextButton("$editor.savemap", "icon-floppy-16", isize, this::save);
t.addImageTextButton("$editor.savemap", Icon.floppy16Small, this::save);
t.addImageTextButton("$editor.mapinfo", "icon-pencil", isize, () -> {
t.addImageTextButton("$editor.mapinfo", Icon.pencilSmall, () -> {
infoDialog.show();
menu.hide();
});
t.row();
t.addImageTextButton("$editor.generate", "icon-editor", isize, () -> {
t.addImageTextButton("$editor.generate", Icon.editorSmall, () -> {
generateDialog.show(generateDialog::applyToEditor);
menu.hide();
});
t.addImageTextButton("$editor.resize", "icon-resize", isize, () -> {
t.addImageTextButton("$editor.resize", Icon.resizeSmall, () -> {
resizeDialog.show();
menu.hide();
});
t.row();
if(!ios){
t.addImageTextButton("$editor.import", "icon-load-map", isize, () ->
createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", "icon-load-map", (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", "icon-file", (Runnable)() ->
platform.showFileChooser("$editor.loadmap", "Map Files", file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
}else if(file.extension().equalsIgnoreCase(oldMapExtension)){
editor.beginEdit(maps.makeLegacyMap(file));
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
});
}), true, FileChooser.anyMapFiles),
"$editor.importimage", "$editor.importimage.description", "icon-file-image", (Runnable)() ->
platform.showFileChooser("$loadimage", "Image Files", file ->
ui.loadAnd(() -> {
try{
Pixmap pixmap = new Pixmap(file);
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, true)));
Log.err(e);
t.addImageTextButton("$editor.import", Icon.loadMapSmall, () ->
createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", Icon.loadMap, (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
}), true, FileChooser.pngFiles))
);
}
});
})),
Cell cell = t.addImageTextButton("$editor.export", "icon-save-map", isize, () -> {
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> {
try{
Pixmap pixmap = new Pixmap(file);
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showException("$editor.errorload", e);
Log.err(e);
}
})))
);
t.addImageTextButton("$editor.export", Icon.saveMapSmall, () -> {
if(!ios){
platform.showFileChooser("$editor.savemap", "Map Files", file -> {
file = file.parent().child(file.nameWithoutExtension() + "." + mapExtension);
FileHandle result = file;
platform.showFileChooser(false, mapExtension, file -> {
ui.loadAnd(() -> {
try{
if(!editor.getTags().containsKey("name")){
editor.getTags().put("name", result.nameWithoutExtension());
editor.getTags().put("name", file.nameWithoutExtension());
}
MapIO.writeMap(result, editor.createMap(result));
MapIO.writeMap(file, editor.createMap(file));
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, true)));
ui.showException("$editor.errorsave", e);
Log.err(e);
}
});
}, false, FileChooser.mapFiles);
});
}else{
ui.loadAnd(() -> {
try{
@@ -142,25 +135,43 @@ public class MapEditorDialog extends Dialog implements Disposable{
MapIO.writeMap(result, editor.createMap(result));
platform.shareFile(result);
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, true)));
ui.showException("$editor.errorsave", e);
Log.err(e);
}
});
}
});
if(ios){
cell.size(swidth * 2f + 10, 60f).colspan(2);
}
});
menu.cont.row();
menu.cont.addImageTextButton("$editor.ingame", "icon-arrow", isize, this::playtest).padTop(-5).size(swidth * 2f + 10, 60f);
if(steam){
menu.cont.addImageTextButton("$editor.publish.workshop", Icon.linkSmall, () -> {
Map map = save();
if(map == null) return;
if(map.tags.get("description", "").length() < 4){
ui.showErrorMessage("$editor.nodescription");
return;
}
if(!Structs.contains(Gamemode.all, g -> g.valid(map))){
ui.showErrorMessage("$map.nospawn");
return;
}
platform.publishMap(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? "$view.workshop" : "$editor.publish.workshop"));
menu.cont.row();
}
menu.cont.addImageTextButton("$editor.ingame", Icon.arrowSmall, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.row();
menu.cont.addImageTextButton("$quit", "icon-back", isize, () -> {
menu.cont.addImageTextButton("$quit", Icon.backSmall, () -> {
tryExit();
menu.hide();
}).size(swidth * 2f + 10, 60f);
@@ -177,7 +188,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
try{
editor.beginEdit(map);
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, true)));
ui.showException("$editor.errorload", e);
Log.err(e);
}
}));
@@ -186,7 +197,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
clearChildren();
margin(0);
shown(this::build);
update(() -> {
if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){
@@ -228,11 +238,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
platform.updateRPC();
if(!Core.settings.getBool("landscape")) platform.endForceLandscape();
});
}
@Override
protected void drawBackground(float x, float y){
drawDefaultBackground(x, y);
shown(this::build);
}
public void resumeEditing(){
@@ -253,6 +260,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
state.teams = new Teams();
player.reset();
state.rules = Gamemode.editor.apply(lastSavedRules.copy());
state.rules.zone = null;
world.setMap(new Map(StringMap.of(
"name", "Editor Playtesting",
"width", editor.width(),
@@ -274,33 +282,39 @@ public class MapEditorDialog extends Dialog implements Disposable{
});
}
private void save(){
public Map save(){
boolean isEditor = state.rules.editor;
state.rules.editor = false;
String name = editor.getTags().get("name", "").trim();
editor.getTags().put("rules", JsonIO.write(state.rules));
editor.getTags().remove("width");
editor.getTags().remove("height");
player.dead = true;
Map returned = null;
if(name.isEmpty()){
infoDialog.show();
Core.app.post(() -> ui.showError("$editor.save.noname"));
Core.app.post(() -> ui.showErrorMessage("$editor.save.noname"));
}else{
Map map = maps.all().find(m -> m.name().equals(name));
if(map != null && !map.custom){
handleSaveBuiltin(map);
}else{
maps.saveMap(editor.getTags());
returned = maps.saveMap(editor.getTags());
ui.showInfoFade("$editor.saved");
}
}
menu.hide();
saved = true;
state.rules.editor = isEditor;
return returned;
}
/** Called when a built-in map save is attempted.*/
protected void handleSaveBuiltin(Map map){
ui.showError("$editor.save.overwrite");
ui.showErrorMessage("$editor.save.overwrite");
}
/**
@@ -320,7 +334,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
for(int i = 0; i < arguments.length; i += 4){
String name = (String)arguments[i];
String description = (String)arguments[i + 1];
String iconname = (String)arguments[i + 2];
Drawable iconname = (Drawable)arguments[i + 2];
Runnable listenable = (Runnable)arguments[i + 3];
TextButton button = dialog.cont.addButton(name, () -> {
@@ -330,11 +344,11 @@ public class MapEditorDialog extends Dialog implements Disposable{
}).left().margin(0).get();
button.clearChildren();
button.addImage(iconname).size(iconsize).padLeft(10);
button.addImage(iconname).padLeft(10);
button.table(t -> {
t.add(name).growX().wrap();
t.row();
t.add(description).color(Color.GRAY).growX().wrap();
t.add(description).color(Color.gray).growX().wrap();
}).growX().pad(10f).padLeft(5);
button.row();
@@ -369,7 +383,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
show();
}catch(Exception e){
Log.err(e);
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, true)));
ui.showException("$editor.errorload", e);
}
});
}
@@ -407,14 +421,13 @@ public class MapEditorDialog extends Dialog implements Disposable{
Consumer<EditorTool> addTool = tool -> {
ImageButton button = new ImageButton("icon-" + tool.name() + "-small", "clear-toggle");
ImageButton button = new ImageButton(Core.atlas.drawable("icon-" + tool.name() + "-small"), Styles.clearTogglei);
button.clicked(() -> {
view.setTool(tool);
if(lastTable[0] != null){
lastTable[0].remove();
}
});
button.resizeImage(iconsizesmall);
button.update(() -> button.setChecked(view.getTool() == tool));
group.add(button);
@@ -434,7 +447,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
lastTable[0].remove();
}
Table table = new Table("dialogDim");
Table table = new Table(Styles.black9);
table.defaults().size(300f, 70f);
for(int i = 0; i < tool.altModes.length; i++){
@@ -444,10 +457,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
table.addButton(b -> {
b.left();
b.marginLeft(6);
b.setStyle(Core.scene.skin.get("clear-toggle", TextButtonStyle.class));
b.setStyle(Styles.clearTogglet);
b.add(Core.bundle.get("toolmode." + name)).left();
b.row();
b.add(Core.bundle.get("toolmode." + name + ".description")).color(Color.LIGHT_GRAY).left();
b.add(Core.bundle.get("toolmode." + name + ".description")).color(Color.lightGray).left();
}, () -> {
tool.mode = (tool.mode == mode ? -1 : mode);
table.remove();
@@ -484,16 +497,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
tools.defaults().size(size, size);
tools.addImageButton("icon-menu-large-small", "clear", iconsizesmall, menu::show);
tools.addImageButton(Icon.menuLargeSmall, Styles.cleari, menu::show);
ImageButton grid = tools.addImageButton("icon-grid-small", "clear-toggle", iconsizesmall, () -> view.setGrid(!view.isGrid())).get();
ImageButton grid = tools.addImageButton(Icon.gridSmall, Styles.clearTogglei, () -> view.setGrid(!view.isGrid())).get();
addTool.accept(EditorTool.zoom);
tools.row();
ImageButton undo = tools.addImageButton("icon-undo-small", "clear", iconsizesmall, editor::undo).get();
ImageButton redo = tools.addImageButton("icon-redo-small", "clear", iconsizesmall, editor::redo).get();
ImageButton undo = tools.addImageButton(Icon.undoSmall, Styles.cleari, editor::undo).get();
ImageButton redo = tools.addImageButton(Icon.redoSmall, Styles.cleari, editor::redo).get();
addTool.accept(EditorTool.pick);
@@ -502,8 +515,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
undo.setDisabled(() -> !editor.canUndo());
redo.setDisabled(() -> !editor.canRedo());
undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.GRAY : Color.WHITE));
redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.GRAY : Color.WHITE));
undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.gray : Color.white));
redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.gray : Color.white));
grid.update(() -> grid.setChecked(view.isGrid()));
addTool.accept(EditorTool.line);
@@ -515,7 +528,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
addTool.accept(EditorTool.fill);
addTool.accept(EditorTool.spray);
ImageButton rotate = tools.addImageButton("icon-arrow-16-small", "clear", iconsizesmall, () -> editor.rotation = (editor.rotation + 1) % 4).get();
ImageButton rotate = tools.addImageButton(Icon.arrow16Small, Styles.cleari, () -> editor.rotation = (editor.rotation + 1) % 4).get();
rotate.getImage().update(() -> {
rotate.getImage().setRotation(editor.rotation * 90);
rotate.getImage().setOrigin(Align.center);
@@ -523,7 +536,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
tools.row();
tools.table("underline", t -> t.add("$editor.teams"))
tools.table(Tex.underline, t -> t.add("$editor.teams"))
.colspan(3).height(40).width(size * 3f + 3f).padBottom(3);
tools.row();
@@ -533,7 +546,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
int i = 0;
for(Team team : Team.all){
ImageButton button = new ImageButton("whiteui", "clear-toggle-partial");
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearTogglePartiali);
button.margin(4f);
button.getImageCell().grow();
button.getStyle().imageUpColor = team.color;
@@ -549,7 +562,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
mid.row();
mid.table("underline", t -> {
mid.table(Tex.underline, t -> {
Slider slider = new Slider(0, MapEditor.brushSizes.length - 1, 1, false);
slider.moved(f -> editor.brushSize = MapEditor.brushSizes[(int)(float)f]);
for(int j = 0; j < MapEditor.brushSizes.length; j++){
@@ -578,7 +591,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(Core.input.ctrl()){
//alt mode select
//TODO these keycode are unusable, tweak later
for(int i = 0; i < view.getTool().altModes.length + 1; i++){
if(Core.input.keyTap(KeyCode.valueOf("NUM_" + (i + 1)))){
view.getTool().mode = i - 1;
@@ -681,11 +693,11 @@ public class MapEditorDialog extends Dialog implements Disposable{
});
for(Block block : blocksOut){
TextureRegion region = block.icon(Icon.medium);
TextureRegion region = block.icon(Block.Icon.medium);
if(!Core.atlas.isFound(region)) continue;
ImageButton button = new ImageButton("whiteui", "clear-toggle");
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearTogglei);
button.getStyle().imageUp = new TextureRegionDrawable(region);
button.clicked(() -> editor.drawBlock = block);
button.resizeImage(8 * 4f);
@@ -700,7 +712,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
group.getButtons().get(2).setChecked(true);
table.table("underline", extra -> extra.labelWrap(() -> editor.drawBlock.localizedName).width(200f).center()).growX();
table.table(Tex.underline, extra -> extra.labelWrap(() -> editor.drawBlock.localizedName).width(200f).center()).growX();
table.row();
table.add(pane).growY().fillX();
}

View File

@@ -8,10 +8,12 @@ import io.anuke.arc.graphics.Pixmap.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.ImageButton.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.async.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.filters.*;
@@ -84,7 +86,7 @@ public class MapGenerateDialog extends FloatingDialog{
update();
}).size(160f, 64f);
buttons.addImageTextButton("$add", "icon-add", iconsize, this::showAdd).height(64f).width(140f);
buttons.addImageTextButton("$add", Icon.add, this::showAdd).height(64f).width(140f);
if(!applied){
hidden(this::apply);
@@ -176,10 +178,8 @@ public class MapGenerateDialog extends FloatingDialog{
}
}
}, new Stack(){{
add(new Image("loadDim"));
add(new Image("icon-refresh"){{
setScaling(Scaling.none);
}});
add(new Image(Styles.black8));
add(new Image(Icon.refresh, Scaling.none));
visible(() -> generating && !updateEditorOnChange);
}}).grow().padRight(10);
t.pane(p -> filterTable = p.marginRight(6)).update(pane -> {
@@ -216,7 +216,7 @@ public class MapGenerateDialog extends FloatingDialog{
}
void rebuildFilters(){
int cols = Math.max((int)(Math.max(filterTable.getParent().getWidth(), Core.graphics.getWidth()/2f * 0.9f) / UnitScl.dp.scl(290f)), 1);
int cols = Math.max((int)(Math.max(filterTable.getParent().getWidth(), Core.graphics.getWidth()/2f * 0.9f) / Scl.scl(290f)), 1);
filterTable.clearChildren();
filterTable.top().left();
int i = 0;
@@ -224,7 +224,7 @@ public class MapGenerateDialog extends FloatingDialog{
for(GenerateFilter filter : filters){
//main container
filterTable.table("button", c -> {
filterTable.table(Tex.button, c -> {
//icons to perform actions
c.table(t -> {
t.top();
@@ -233,26 +233,26 @@ public class MapGenerateDialog extends FloatingDialog{
t.row();
t.table(b -> {
String style = "clear";
ImageButtonStyle style = Styles.cleari;
b.defaults().size(50f);
b.addImageButton("icon-refresh-small", style, iconsizesmall, () -> {
b.addImageButton(Icon.refreshSmall, style, () -> {
filter.randomize();
update();
});
b.addImageButton("icon-arrow-up-small", style, iconsizesmall, () -> {
b.addImageButton(Icon.arrowUpSmall, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.max(0, idx - 1));
rebuildFilters();
update();
});
b.addImageButton("icon-arrow-down-small",style, iconsizesmall, () -> {
b.addImageButton(Icon.arrowDownSmall, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
rebuildFilters();
update();
});
b.addImageButton("icon-trash-small", style, iconsizesmall, () -> {
b.addImageButton(Icon.trashSmall, style, () -> {
filters.remove(filter);
rebuildFilters();
update();

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.scene.ui.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.dialogs.*;
public class MapInfoDialog extends FloatingDialog{
@@ -42,7 +43,7 @@ public class MapInfoDialog extends FloatingDialog{
t.row();
t.add("$editor.description").padRight(8).left();
TextArea description = t.addArea(tags.get("description", ""), "textarea", text -> {
TextArea description = t.addArea(tags.get("description", ""), Styles.areaField, text -> {
tags.put("description", text);
}).size(400f, 140f).get();

View File

@@ -48,13 +48,13 @@ public class MapLoadDialog extends FloatingDialog{
table.defaults().size(200f, 90f).pad(4f);
table.margin(10f);
ScrollPane pane = new ScrollPane(table, "horizontal");
ScrollPane pane = new ScrollPane(table, Styles.horizontalPane);
pane.setFadeScrollBars(false);
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);
TextButton button = new TextButton(map.name(), Styles.togglet);
button.add(new BorderImage(map.safeTexture(), 2f).setScaling(Scaling.fit)).size(16 * 4f);
button.getCells().reverse();
button.clicked(() -> selected = map);
button.getLabelCell().grow().left().padLeft(5f);

View File

@@ -155,7 +155,7 @@ public class MapRenderer implements Disposable{
}
mesh.draw(idxDecal, region, wx * tilesize + offsetX, wy * tilesize + offsetY, width, height);
mesh.setColor(Color.WHITE);
mesh.setColor(Color.white);
}
@Override

View File

@@ -1,9 +1,10 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.function.IntPositionConsumer;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.arc.function.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.ui.dialogs.*;
public class MapResizeDialog extends FloatingDialog{
private static final int minSize = 50, maxSize = 500, increment = 50;
@@ -28,7 +29,7 @@ public class MapResizeDialog extends FloatingDialog{
height = move(height, -1);
}).size(60f);
table.table("button", t -> t.label(() -> (w ? width : height) + "")).width(200);
table.table(Tex.button, t -> t.label(() -> (w ? width : height) + "")).width(200);
table.addButton(">", () -> {
if(w)

View File

@@ -55,7 +55,7 @@ public class MapSaveDialog extends FloatingDialog{
if(!invalid()){
listener.accept(field.getText());
}else{
ui.showError("$editor.failoverwrite");
ui.showErrorMessage("$editor.failoverwrite");
}
}

View File

@@ -11,7 +11,7 @@ import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.Element;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.ui.TextField;
import io.anuke.arc.scene.ui.layout.UnitScl;
import io.anuke.arc.scene.ui.layout.Scl;
import io.anuke.arc.util.*;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.input.Binding;
@@ -241,7 +241,7 @@ public class MapView extends Element implements GestureListener{
Draw.reset();
if(grid){
Draw.color(Color.GRAY);
Draw.color(Color.gray);
image.setBounds(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
image.draw();
Draw.color();
@@ -258,7 +258,7 @@ public class MapView extends Element implements GestureListener{
float scaling = zoom * Math.min(width, height) / editor.width();
Draw.color(Pal.accent);
Lines.stroke(UnitScl.dp.scl(2f));
Lines.stroke(Scl.scl(2f));
if((!editor.drawBlock.isMultiblock() || tool == EditorTool.eraser) && tool != EditorTool.fill){
if(tool == EditorTool.line && drawing){
@@ -294,7 +294,7 @@ public class MapView extends Element implements GestureListener{
}
Draw.color(Pal.accent);
Lines.stroke(UnitScl.dp.scl(3f));
Lines.stroke(Scl.scl(3f));
Lines.rect(x, y, width, height);
Draw.reset();
@@ -320,7 +320,7 @@ public class MapView extends Element implements GestureListener{
public boolean zoom(float initialDistance, float distance){
if(!active()) return false;
float nzoom = distance - initialDistance;
zoom += nzoom / 10000f / UnitScl.dp.scl(1f) * zoom;
zoom += nzoom / 10000f / Scl.scl(1f) * zoom;
clampZoom();
return false;
}

View File

@@ -1,22 +1,23 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.input.KeyCode;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.scene.event.Touchable;
import io.anuke.arc.scene.ui.Label;
import io.anuke.arc.scene.ui.TextField.TextFieldFilter;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.input.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.TextField.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.io.JsonIO;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.game.SpawnGroup.never;
@@ -61,7 +62,8 @@ public class WaveInfoDialog extends FloatingDialog{
groups = maps.readWaves(Core.app.getClipboardText());
buildGroups();
}catch(Exception e){
ui.showError("$waves.invalid");
e.printStackTrace();
ui.showErrorMessage("$waves.invalid");
}
dialog.hide();
}).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty());
@@ -79,7 +81,7 @@ public class WaveInfoDialog extends FloatingDialog{
groups = JsonIO.copy(state.rules.spawns.isEmpty() ? defaultWaves.get() : state.rules.spawns);
cont.clear();
cont.stack(new Table("clear", main -> {
cont.stack(new Table(Tex.clear, main -> {
main.pane(t -> table = t).growX().growY().padRight(8f).get().setScrollingDisabled(true, false);
main.row();
main.addButton("$add", () -> {
@@ -94,8 +96,8 @@ public class WaveInfoDialog extends FloatingDialog{
setAlignment(Align.center, Align.center);
}}).width(390f).growY();
cont.table("clear", m -> {
m.add("$waves.preview").color(Color.LIGHT_GRAY).growX().center().get().setAlignment(Align.center, Align.center);
cont.table(Tex.clear, m -> {
m.add("$waves.preview").color(Color.lightGray).growX().center().get().setAlignment(Align.center, Align.center);
m.row();
m.addButton("-", () -> {
}).update(t -> {
@@ -134,7 +136,7 @@ public class WaveInfoDialog extends FloatingDialog{
if(groups != null){
for(SpawnGroup group : groups){
table.table("button", t -> {
table.table(Tex.button, t -> {
t.margin(0).defaults().pad(3).padLeft(5f).growX().left();
t.addButton(b -> {
b.left();
@@ -238,7 +240,7 @@ public class WaveInfoDialog extends FloatingDialog{
for(int i = start; i < displayed + start; i++){
int wave = i;
preview.table("underline", table -> {
preview.table(Tex.underline, table -> {
table.add((wave + 1) + "").color(Pal.accent).center().colspan(2).get().setAlignment(Align.center, Align.center);
table.row();
@@ -252,7 +254,7 @@ public class WaveInfoDialog extends FloatingDialog{
if(spawned[j] > 0){
UnitType type = content.getByID(ContentType.unit, j);
table.addImage(type.iconRegion).size(30f).padRight(4);
table.add(spawned[j] + "x").color(Color.LIGHT_GRAY).padRight(6);
table.add(spawned[j] + "x").color(Color.lightGray).padRight(6);
table.row();
}
}

View File

@@ -1,20 +1,22 @@
package io.anuke.mindustry.entities;
import io.anuke.annotations.Annotations.Struct;
import io.anuke.arc.*;
import io.anuke.arc.collection.GridBits;
import io.anuke.arc.collection.IntQueue;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.Bullets;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.effect.Fire;
import io.anuke.mindustry.entities.effect.Lightning;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.gen.PropCell;
@@ -187,6 +189,10 @@ public class Damage{
//TODO better velocity displacement
float dst = tr.set(entity.x - x, entity.y - y).len();
entity.velocity().add(tr.setLength((1f - dst / radius) * 2f / entity.mass()));
if(complete && damage >= 9999999f && entity == player){
Events.fire(Trigger.exclusionDeath);
}
};
rect.setSize(radius * 2).setCenter(x, y);

View File

@@ -7,7 +7,7 @@ import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Position;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.entities.impl.EffectEntity;
import io.anuke.mindustry.entities.type.EffectEntity;
import io.anuke.mindustry.entities.traits.ScaleTrait;
public class Effects{
@@ -49,7 +49,7 @@ public class Effects{
}
public static void effect(Effect effect, float x, float y, float rotation){
provider.createEffect(effect, Color.WHITE, x, y, rotation, null);
provider.createEffect(effect, Color.white, x, y, rotation, null);
}
public static void effect(Effect effect, float x, float y){
@@ -61,7 +61,7 @@ public class Effects{
}
public static void effect(Effect effect, Position loc){
provider.createEffect(effect, Color.WHITE, loc.getX(), loc.getY(), 0f, null);
provider.createEffect(effect, Color.white, loc.getX(), loc.getY(), 0f, null);
}
public static void effect(Effect effect, Color color, float x, float y, float rotation){
@@ -73,7 +73,7 @@ public class Effects{
}
public static void effect(Effect effect, float x, float y, float rotation, Object data){
provider.createEffect(effect, Color.WHITE, x, y, rotation, data);
provider.createEffect(effect, Color.white, x, y, rotation, data);
}
/** Default value is 1000. Higher numbers mean more powerful shake (less falloff). */

View File

@@ -18,6 +18,8 @@ public class EntityGroup<T extends Entity>{
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 final Array<T> intersectArray = new Array<>();
private final Rectangle intersectRect = new Rectangle();
private IntMap<T> map;
private QuadTree tree;
private Consumer<T> removeListener;
@@ -161,6 +163,15 @@ public class EntityGroup<T extends Entity>{
tree().getIntersect(out, x, y, width, height);
}
@SuppressWarnings("unchecked")
public Array<T> intersect(float x, float y, float width, float height){
intersectArray.clear();
//don't waste time for empty groups
if(isEmpty()) return intersectArray;
tree().getIntersect(intersectArray, intersectRect.set(x, y, width, height));
return intersectArray;
}
public QuadTree tree(){
if(!useTree) throw new RuntimeException("This group does not support quadtrees! Enable quadtrees when creating it.");
return tree;

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.gen.*;
//TODO scale velocity depending on fslope()
@@ -20,7 +21,7 @@ public class ArtilleryBulletType extends BasicBulletType{
}
@Override
public void update(Bullet b){
public void update(io.anuke.mindustry.entities.type.Bullet b){
super.update(b);
if(b.timer.get(0, 3 + b.fslope() * 2f)){

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.Core;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.graphics.Pal;
/** An extended BulletType for most ammo-based bullets shot from turrets and units. */

View File

@@ -7,6 +7,7 @@ import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.type.Bullet;
public abstract class FlakBulletType extends BasicBulletType{
protected static Rectangle rect = new Rectangle();

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.math.geom.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@@ -35,7 +36,7 @@ public class LiquidBulletType extends BulletType{
}
@Override
public void update(Bullet b){
public void update(io.anuke.mindustry.entities.type.Bullet b){
super.update(b);
if(liquid.canExtinguish()){
@@ -49,8 +50,8 @@ public class LiquidBulletType extends BulletType{
}
@Override
public void draw(Bullet b){
Draw.color(liquid.color, Color.WHITE, b.fout() / 100f);
public void draw(io.anuke.mindustry.entities.type.Bullet b){
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, 0.5f + b.fout() * 2.5f);
}

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
@@ -23,7 +24,7 @@ public class MassDriverBolt extends BulletType{
}
@Override
public void draw(Bullet b){
public void draw(io.anuke.mindustry.entities.type.Bullet b){
float w = 11f, h = 13f;
Draw.color(Pal.bulletYellowBack);
@@ -36,7 +37,7 @@ public class MassDriverBolt extends BulletType{
}
@Override
public void update(Bullet b){
public void update(io.anuke.mindustry.entities.type.Bullet b){
//data MUST be an instance of DriverBulletData
if(!(b.getData() instanceof DriverBulletData)){
hit(b);
@@ -82,7 +83,7 @@ public class MassDriverBolt extends BulletType{
}
@Override
public void despawned(Bullet b){
public void despawned(io.anuke.mindustry.entities.type.Bullet b){
super.despawned(b);
if(!(b.getData() instanceof DriverBulletData)) return;
@@ -93,7 +94,7 @@ public class MassDriverBolt extends BulletType{
int amountDropped = Mathf.random(0, data.items[i]);
if(amountDropped > 0){
float angle = b.rot() + Mathf.range(100f);
Effects.effect(Fx.dropItem, Color.WHITE, b.x, b.y, angle, content.item(i));
Effects.effect(Fx.dropItem, Color.white, b.x, b.y, angle, content.item(i));
}
}
}

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.Pal;

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.entities.effect;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.TimedEntity;
import io.anuke.mindustry.entities.type.TimedEntity;
import io.anuke.mindustry.entities.traits.BelowLiquidTrait;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.mindustry.graphics.Pal;

View File

@@ -1,18 +1,19 @@
package io.anuke.mindustry.entities.effect;
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.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.impl.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.type.TimedEntity;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.world.*;
import java.io.*;
@@ -40,7 +41,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait{
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
public static void create(Tile tile){
if(Net.client() || tile == null) return; //not clientside.
if(net.client() || tile == null) return; //not clientside.
Fire fire = map.get(tile.pos());
@@ -70,7 +71,11 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait{
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.pos())){
map.get(tile.pos()).time += intensity * Time.delta();
Fire fire = map.get(tile.pos());
fire.time += intensity * Time.delta();
if(fire.time >= fire.lifetime()){
Events.fire(Trigger.fireExtinguish);
}
}
}
@@ -106,7 +111,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait{
time = Mathf.clamp(time + Time.delta(), 0, lifetime());
map.put(tile.pos(), this);
if(Net.client()){
if(net.client()){
return;
}

View File

@@ -6,7 +6,7 @@ import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.Effects.EffectRenderer;
import io.anuke.mindustry.entities.impl.EffectEntity;
import io.anuke.mindustry.entities.type.EffectEntity;
import io.anuke.mindustry.world.Tile;
/**

View File

@@ -10,14 +10,14 @@ import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.TimedEntity;
import io.anuke.mindustry.entities.type.TimedEntity;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Tile;
import static io.anuke.mindustry.Vars.effectGroup;
import static io.anuke.mindustry.Vars.*;
public class ItemTransfer extends TimedEntity implements DrawTrait{
private Vector2 from = new Vector2();

View File

@@ -12,8 +12,8 @@ import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.content.Bullets;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.impl.TimedEntity;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.type.TimedEntity;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.mindustry.entities.traits.TimeTrait;
import io.anuke.mindustry.entities.type.Unit;
@@ -47,7 +47,7 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
}
/** Do not invoke! */
@Remote(called = Loc.server)
@Remote(called = Loc.server, unreliable = true)
public static void createLighting(int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
Lightning l = Pools.obtain(Lightning.class, Lightning::new);
@@ -110,7 +110,7 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
@Override
public void draw(){
Lines.stroke(3f * fout());
Draw.color(color, Color.WHITE, fin());
Draw.color(color, Color.white, fin());
Lines.beginLine();
Lines.linePoint(x, y);

View File

@@ -14,11 +14,10 @@ import io.anuke.arc.util.pooling.Pool.Poolable;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.impl.SolidEntity;
import io.anuke.mindustry.entities.type.SolidEntity;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.TypeID;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Liquid;
import io.anuke.mindustry.world.Tile;
@@ -83,7 +82,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
Puddle p = map.get(tile.pos());
if(p == null){
if(Net.client()) return; //not clientside.
if(net.client()) return; //not clientside.
Puddle puddle = Pools.obtain(Puddle.class, Puddle::new);
puddle.tile = tile;
@@ -168,7 +167,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
public void update(){
//no updating happens clientside
if(Net.client()){
if(net.client()){
amount = Mathf.lerpDelta(amount, targetAmount, 0.15f);
}else{
//update code

View File

@@ -88,7 +88,7 @@ public interface MinerTrait extends Entity{
float ex = tile.worldx() + Mathf.sin(Time.time() + 48, swingScl, swingMag);
float ey = tile.worldy() + Mathf.sin(Time.time() + 48, swingScl + 2f, swingMag);
Draw.color(Color.LIGHT_GRAY, Color.WHITE, 1f - flashScl + Mathf.absin(Time.time(), 0.5f, flashScl));
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time(), 0.5f, flashScl));
Drawf.laser(Core.atlas.find("minelaser"), Core.atlas.find("minelaser-end"), px, py, ex, ey, 0.75f);

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.math.geom.Position;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.world.Tile;
public interface SpawnerTrait extends TargetTrait, Position{
@@ -9,6 +9,8 @@ public interface SpawnerTrait extends TargetTrait, Position{
void updateSpawning(Player unit);
boolean hasUnit(Unit unit);
@Override
default boolean isValid(){
return getTile().entity instanceof SpawnerTrait;

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.impl;
package io.anuke.mindustry.entities.type;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.Entity;

View File

@@ -11,11 +11,13 @@ import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
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.*;
import io.anuke.mindustry.world.blocks.defense.DeflectorWall.*;
import io.anuke.mindustry.world.blocks.units.CommandCenter.*;
import io.anuke.mindustry.world.blocks.units.UnitFactory.*;
import io.anuke.mindustry.world.meta.*;
@@ -48,7 +50,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
public static void onUnitDeath(BaseUnit unit){
if(unit == null) return;
if(Net.server() || !Net.active()){
if(net.server() || !net.active()){
UnitDrops.dropItems(unit);
}
@@ -56,7 +58,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
unit.type.deathSound.at(unit);
//visual only.
if(Net.client()){
if(net.client()){
Tile tile = world.tile(unit.spawner);
if(tile != null){
tile.block().unitRemoved(tile, unit);
@@ -79,6 +81,17 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
return type.typeID;
}
@Override
public void onHit(SolidTrait entity){
if(entity instanceof Bullet && ((Bullet)entity).getOwner() instanceof DeflectorEntity && player != null && getTeam() != player.getTeam()){
Core.app.post(() -> {
if(isDead()){
Events.fire(Trigger.phaseDeflectHit);
}
});
}
}
public @Nullable Tile getSpawner(){
return world.tile(spawner);
}
@@ -87,7 +100,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
return indexer.getAllied(team, BlockFlag.comandCenter).size != 0 && indexer.getAllied(team, BlockFlag.comandCenter).first().entity instanceof CommandCenterEntity;
}
public UnitCommand getCommand(){
public @Nullable UnitCommand getCommand(){
if(isCommanded()){
return indexer.getAllied(team, BlockFlag.comandCenter).first().<CommandCenterEntity>entity().command;
}
@@ -161,8 +174,15 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
}
public TileEntity getClosestEnemyCore(){
public Tile getClosest(BlockFlag flag){
return Geometry.findClosest(x, y, indexer.getAllied(team, flag));
}
public Tile getClosestSpawner(){
return Geometry.findClosest(x, y, Vars.spawner.getGroundSpawns());
}
public TileEntity getClosestEnemyCore(){
for(Team enemy : Vars.state.teams.enemiesOf(team)){
Tile tile = Geometry.findClosest(x, y, Vars.state.teams.get(enemy).cores);
if(tile != null){
@@ -255,13 +275,13 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
hitTime -= Time.delta();
if(Net.client()){
if(net.client()){
interpolate();
status.update(this);
return;
}
if(!isFlying() && (world.tileWorld(x, y) != null && world.tileWorld(x, y).solid())){
if(!isFlying() && (world.tileWorld(x, y) != null && !(world.tileWorld(x, y).block() instanceof BuildBlock) && world.tileWorld(x, y).solid())){
kill();
}
@@ -297,7 +317,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
public void removed(){
super.removed();
Tile tile = world.tile(spawner);
if(tile != null && !Net.client()){
if(tile != null && !net.client()){
tile.block().unitRemoved(tile, this);
}

View File

@@ -1,23 +1,19 @@
package io.anuke.mindustry.entities.bullet;
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.Pool.Poolable;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.effect.Lightning;
import io.anuke.mindustry.entities.impl.SolidEntity;
import io.anuke.arc.util.pooling.Pool.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.bulletGroup;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Poolable, DrawTrait, VelocityTrait, TimeTrait, TeamTrait, AbsorbTrait{
public Interval timer = new Interval(3);
@@ -25,7 +21,7 @@ public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Pool
private float lifeScl;
private Team team;
private Object data;
private boolean supressCollision, supressOnce, initialized;
private boolean supressCollision, supressOnce, initialized, deflected;
protected BulletType type;
protected Entity owner;
@@ -100,9 +96,14 @@ public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Pool
return type.collidesTiles;
}
public void supress(){
public void deflect(){
supressCollision = true;
supressOnce = true;
deflected = true;
}
public boolean isDeflected(){
return deflected;
}
public BulletType getBulletType(){
@@ -239,6 +240,7 @@ public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Pool
data = null;
supressCollision = false;
supressOnce = false;
deflected = false;
initialized = false;
}

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.impl;
package io.anuke.mindustry.entities.type;
import io.anuke.mindustry.entities.traits.*;

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.impl;
package io.anuke.mindustry.entities.type;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.util.pooling.Pool.Poolable;
@@ -13,7 +13,7 @@ import static io.anuke.mindustry.Vars.effectGroup;
public class EffectEntity extends TimedEntity implements Poolable, DrawTrait{
public Effect effect;
public Color color = new Color(Color.WHITE);
public Color color = new Color(Color.white);
public Object data;
public float rotation = 0f;
@@ -63,7 +63,7 @@ public class EffectEntity extends TimedEntity implements Poolable, DrawTrait{
@Override
public void reset(){
effect = null;
color.set(Color.WHITE);
color.set(Color.white);
rotation = time = poffsetx = poffsety = 0f;
parent = null;
data = null;

View File

@@ -5,11 +5,11 @@ 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.graphics.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.meta.*;
@@ -36,13 +36,15 @@ public abstract class FlyingUnit extends BaseUnit{
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
if(target == null){
setState(patrol);
}
}
if(target != null){
if(target == null){
target = getSpawner();
}
if(target == getSpawner() && getSpawner() != null){
circle(80f + Mathf.randomSeed(id) * 120);
}else if(target != null){
attack(type.attackLength);
if((Angles.near(angleTo(target), rotation, type.shootCone) || getWeapon().ignoreRotation) //bombers and such don't care about rotation
@@ -65,26 +67,28 @@ public abstract class FlyingUnit extends BaseUnit{
getWeapon().update(FlyingUnit.this, to.x, to.y);
}
}
}else{
target = getClosestSpawner();
moveTo(Vars.state.rules.dropZoneRadius + 120f);
}
}
},
patrol = new UnitState(){
rally = new UnitState(){
public void update(){
if(retarget()){
targetClosestAllyFlag(BlockFlag.rally);
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
if(target != null && !Units.invalidateTarget(target, team, x, y)){
setState(attack);
return;
}
target = getSpawner();
if(target == null) target = getClosestCore();
if(target == null) target = getSpawner();
}
if(target != null){
circle(80f + Mathf.randomSeed(id) * 120);
circle(65f + Mathf.randomSeed(id) * 100);
}
}
},
@@ -110,7 +114,7 @@ public abstract class FlyingUnit extends BaseUnit{
public void onCommand(UnitCommand command){
state.set(command == UnitCommand.retreat ? retreat :
command == UnitCommand.attack ? attack :
command == UnitCommand.patrol ? patrol :
command == UnitCommand.rally ? rally :
null);
}
@@ -123,10 +127,10 @@ public abstract class FlyingUnit extends BaseUnit{
public void update(){
super.update();
if(!Net.client()){
if(!net.client()){
updateRotation();
wobble();
}
wobble();
}
@Override
@@ -136,7 +140,7 @@ public abstract class FlyingUnit extends BaseUnit{
@Override
public void draw(){
Draw.mixcol(Color.WHITE, hitTime / hitDuration);
Draw.mixcol(Color.white, hitTime / hitDuration);
Draw.rect(type.region, x, y, rotation - 90);
drawWeapons();
@@ -153,7 +157,7 @@ public abstract class FlyingUnit extends BaseUnit{
Fill.circle(x + Angles.trnsx(rotation + 180, type.engineOffset), y + Angles.trnsy(rotation + 180, type.engineOffset),
type.engineSize + Mathf.absin(Time.time(), 2f, type.engineSize / 4f));
Draw.color(Color.WHITE);
Draw.color(Color.white);
Fill.circle(x + Angles.trnsx(rotation + 180, type.engineOffset - 1f), y + Angles.trnsy(rotation + 180, type.engineOffset - 1f),
(type.engineSize + Mathf.absin(Time.time(), 2f, type.engineSize / 4f)) / 2f);
Draw.color();
@@ -176,7 +180,7 @@ public abstract class FlyingUnit extends BaseUnit{
}
protected void wobble(){
if(Net.client()) return;
if(net.client()) return;
x += Mathf.sin(Time.time() + id * 999, 25f, 0.05f) * Time.delta();
y += Mathf.cos(Time.time() + id * 999, 25f, 0.05f) * Time.delta();

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
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.units.*;
@@ -13,6 +14,7 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
@@ -34,31 +36,30 @@ public abstract class GroundUnit extends BaseUnit{
TileEntity core = getClosestEnemyCore();
if(core == null){
setState(patrol);
return;
}
Tile closestSpawn = getClosestSpawner();
if(closestSpawn == null || !withinDst(closestSpawn, Vars.state.rules.dropZoneRadius + 85f)){
moveToCore(PathTarget.enemyCores);
}
}else{
float dst = dst(core);
float dst = dst(core);
if(dst < getWeapon().bullet.range() / 1.1f){
target = core;
}
if(dst < getWeapon().bullet.range() / 1.1f){
target = core;
}
if(dst > getWeapon().bullet.range() * 0.5f){
moveToCore();
if(dst > getWeapon().bullet.range() * 0.5f){
moveToCore(PathTarget.enemyCores);
}
}
}
},
patrol = new UnitState(){
rally = new UnitState(){
public void update(){
TileEntity target = getClosestCore();
Tile target = getClosest(BlockFlag.rally);
if(target != null){
if(dst(target) > 400f){
moveAwayFromCore();
}else if(!(!Units.invalidateTarget(GroundUnit.this.target, GroundUnit.this) && dst(GroundUnit.this.target) < getWeapon().bullet.range())){
patrol();
}
if(target != null && dst(target) > 80f){
moveToCore(PathTarget.rallyPoints);
}
}
},
@@ -76,7 +77,7 @@ public abstract class GroundUnit extends BaseUnit{
public void onCommand(UnitCommand command){
state.set(command == UnitCommand.retreat ? retreat :
command == UnitCommand.attack ? attack :
command == UnitCommand.patrol ? patrol :
command == UnitCommand.rally ? rally :
null);
}
@@ -125,14 +126,14 @@ public abstract class GroundUnit extends BaseUnit{
@Override
public void draw(){
Draw.mixcol(Color.WHITE, hitTime / hitDuration);
Draw.mixcol(Color.white, hitTime / hitDuration);
float ft = Mathf.sin(walkTime * type.speed * 5f, 6f, 2f + type.hitsize / 15f);
Floor floor = getFloorOn();
if(floor.isLiquid){
Draw.color(Color.WHITE, floor.color, 0.5f);
Draw.color(Color.white, floor.color, 0.5f);
}
for(int i : Mathf.signs){
@@ -143,9 +144,9 @@ public abstract class GroundUnit extends BaseUnit{
}
if(floor.isLiquid){
Draw.color(Color.WHITE, floor.color, drownTime * 0.4f);
Draw.color(Color.white, floor.color, drownTime * 0.4f);
}else{
Draw.color(Color.WHITE);
Draw.color(Color.white);
}
Draw.rect(type.baseRegion, x, y, baseRotation - 90);
@@ -220,10 +221,10 @@ public abstract class GroundUnit extends BaseUnit{
velocity.add(vec);
}
protected void moveToCore(){
protected void moveToCore(PathTarget path){
Tile tile = world.tileWorld(x, y);
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(team, tile);
Tile targetTile = pathfinder.getTargetTile(tile, team, path);
if(tile == targetTile) return;
@@ -242,11 +243,18 @@ public abstract class GroundUnit extends BaseUnit{
}
}
if(enemy == null){
for(Team team : Vars.state.teams.enemiesOf(team)){
enemy = team;
break;
}
}
if(enemy == null) return;
Tile tile = world.tileWorld(x, y);
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(enemy, tile);
Tile targetTile = pathfinder.getTargetTile(tile, enemy, PathTarget.enemyCores);
TileEntity core = getClosestCore();
if(tile == targetTile || core == null || dst(core) < 120f) return;

View File

@@ -1,35 +1,32 @@
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Queue;
import io.anuke.arc.graphics.Color;
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.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.*;
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.pooling.Pools;
import io.anuke.mindustry.Vars;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.*;
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.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.InputHandler.PlaceDraw;
import io.anuke.mindustry.io.TypeIO;
import io.anuke.mindustry.input.InputHandler.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import java.io.*;
@@ -51,7 +48,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public float baseRotation;
public float pointerX, pointerY;
public String name = "name";
public String uuid, usid;
public @Nullable String uuid, usid;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping;
public float boostHeat, shootHeat, destructTime;
public boolean achievedFlight;
@@ -60,13 +57,13 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public SpawnerTrait spawner, lastSpawner;
public int respawns;
public NetConnection con;
public @Nullable NetConnection con;
public boolean isLocal = false;
public Interval timer = new Interval(6);
public TargetTrait target;
public TargetTrait moveTarget;
public String lastText;
public @Nullable String lastText;
public float textFadeTime;
private float walktime, itemtime;
@@ -230,7 +227,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
@Override
public void damage(float amount){
hitTime = hitDuration;
if(!Net.client()){
if(!net.client()){
health -= calculateDamage(amount);
}
@@ -299,11 +296,11 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
Floor floor = getFloorOn();
Draw.color();
Draw.mixcol(Color.WHITE, hitTime / hitDuration);
Draw.mixcol(Color.white, hitTime / hitDuration);
if(!mech.flying){
if(floor.isLiquid){
Draw.color(Color.WHITE, floor.color, 0.5f);
Draw.color(Color.white, floor.color, 0.5f);
}
float boostTrnsY = -boostHeat * 3f;
@@ -323,9 +320,9 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
}
if(floor.isLiquid){
Draw.color(Color.WHITE, floor.color, drownTime);
Draw.color(Color.white, floor.color, drownTime);
}else{
Draw.color(Color.WHITE);
Draw.color(Color.white);
}
Draw.rect(mech.region, x, y, rotation - 90);
@@ -348,7 +345,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
@Override
public void drawStats(){
Draw.color(Color.BLACK, team.color, healthf() + Mathf.absin(Time.time(), healthf() * 5f, 1f - healthf()));
Draw.color(Color.black, team.color, healthf() + Mathf.absin(Time.time(), healthf() * 5f, 1f - healthf()));
Draw.rect(getPowerCellRegion(), x + Angles.trnsx(rotation, mech.cellTrnsY, 0f), y + Angles.trnsy(rotation, mech.cellTrnsY, 0f), rotation - 90);
Draw.reset();
drawBackItems(itemtime, isLocal);
@@ -370,21 +367,21 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
Fill.circle(x + Angles.trnsx(rotation + 180, mech.engineOffset), y + Angles.trnsy(rotation + 180, mech.engineOffset),
size + Mathf.absin(Time.time(), 2f, size / 4f));
Draw.color(Color.WHITE);
Draw.color(Color.white);
Fill.circle(x + Angles.trnsx(rotation + 180, mech.engineOffset - 1f), y + Angles.trnsy(rotation + 180, mech.engineOffset - 1f),
(size + Mathf.absin(Time.time(), 2f, size / 4f)) / 2f);
Draw.color();
}
public void drawName(){
BitmapFont font = Core.scene.skin.getFont("default");
BitmapFont font = Fonts.def;
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
final float nameHeight = 11;
final float textHeight = 15;
boolean ints = font.usesIntegerPositions();
font.setUseIntegerPositions(false);
font.getData().setScale(0.25f / UnitScl.dp.scl(1f));
font.getData().setScale(0.25f / Scl.scl(1f));
layout.setText(font, name);
Draw.color(0f, 0f, 0f, 0.3f);
Fill.rect(x, y + nameHeight - layout.height / 2, layout.width + 2, layout.height + 3);
@@ -396,9 +393,9 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
if(isAdmin){
float s = 3f;
Draw.color(color.r * 0.5f, color.g * 0.5f, color.b * 0.5f, 1f);
Draw.rect(Core.atlas.find("icon-admin-small"), x + layout.width / 2f + 2 + 1, y + nameHeight - 1.5f, s, s);
Draw.rect(Core.atlas.find("icon-admin-badge"), x + layout.width / 2f + 2 + 1, y + nameHeight - 1.5f, s, s);
Draw.color(color);
Draw.rect(Core.atlas.find("icon-admin-small"), x + layout.width / 2f + 2 + 1, y + nameHeight - 1f, s, s);
Draw.rect(Core.atlas.find("icon-admin-badge"), x + layout.width / 2f + 2 + 1, y + nameHeight - 1f, s, s);
}
if(Core.settings.getBool("playerchat") && ((textFadeTime > 0 && lastText != null) || isTyping)){
@@ -407,7 +404,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
float visualFadeTime = 1f - Mathf.curve(1f - textFadeTime, 0.9f);
font.setColor(1f, 1f, 1f, textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime);
layout.setText(font, text, Color.WHITE, width, Align.bottom, true);
layout.setText(font, text, Color.white, width, Align.bottom, true);
Draw.color(0f, 0f, 0f, 0.3f * (textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime));
Fill.rect(x, y + textHeight + layout.height - layout.height/2f, layout.width + 2, layout.height + 3);
@@ -417,7 +414,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
Draw.reset();
Pools.free(layout);
font.getData().setScale(1f);
font.setColor(Color.WHITE);
font.setColor(Color.white);
font.setUseIntegerPositions(ints);
}
@@ -531,7 +528,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
spawner = null;
}
if(isLocal || Net.server()){
if(isLocal || net.server()){
avoidOthers();
}
@@ -562,7 +559,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
status.update(this); //status effect updating also happens with non locals for effect purposes
updateVelocityStatus(); //velocity too, for visual purposes
if(Net.server()){
if(net.server()){
updateShooting(); //server simulates player shooting
}
return;
@@ -571,7 +568,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
data.unlockContent(mech);
}
if(mobile && !Core.settings.getBool("keyboard")){
if(control.input instanceof MobileInput){
updateTouch();
}else{
updateKeyboard();
@@ -727,50 +724,41 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
//update shooting if not building, not mining and there's ammo left
if(!isBuilding() && getMineTile() == null){
//autofire: mobile only!
if(mobile){
if(target == null){
isShooting = false;
if(Core.settings.getBool("autotarget")){
target = Units.closestTarget(team, x, y, getWeapon().bullet.range(), u -> u.getTeam() != Team.derelict, u -> u.getTeam() != Team.derelict);
//autofire
if(target == null){
isShooting = false;
if(Core.settings.getBool("autotarget")){
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, indexer.getDamaged(Team.sharded));
if(target != null && dst(target) > getWeapon().bullet.range()){
target = null;
}else if(target != null){
target = ((Tile)target).entity;
}
}
if(target != null){
setMineTile(null);
if(mech.canHeal && target == null){
target = Geometry.findClosest(x, y, indexer.getDamaged(Team.sharded));
if(target != null && dst(target) > getWeapon().bullet.range()){
target = null;
}else if(target != null){
target = ((Tile)target).entity;
}
}
}else if(target.isValid() || (target instanceof TileEntity && ((TileEntity)target).damaged() && target.getTeam() == team &&
mech.canHeal && dst(target) < getWeapon().bullet.range())){
//rotate toward and shoot the target
if(mech.turnCursor){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f);
if(target != null){
setMineTile(null);
}
Vector2 intercept = Predict.intercept(this, target, getWeapon().bullet.speed);
pointerX = intercept.x;
pointerY = intercept.y;
updateShooting();
isShooting = true;
}
}else if(target.isValid() || (target instanceof TileEntity && ((TileEntity)target).damaged() && target.getTeam() == team &&
mech.canHeal && dst(target) < getWeapon().bullet.range())){
//rotate toward and shoot the target
if(mech.turnCursor){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f);
}
}else if(isShooting()){
Vector2 vec = Core.input.mouseWorld(control.input.getMouseX(),
control.input.getMouseY());
pointerX = vec.x;
pointerY = vec.y;
Vector2 intercept = Predict.intercept(this, target, getWeapon().bullet.speed);
pointerX = intercept.x;
pointerY = intercept.y;
updateShooting();
isShooting = true;
}
}
}
@@ -785,7 +773,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
Vars.ui.chatfrag.addMessage(text, null);
}
}else{
Call.sendMessage(con.id, text, null, null);
Call.sendMessage(con, text, null, null);
}
}
@@ -799,7 +787,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
Vars.ui.chatfrag.addMessage(text, fromName);
}
}else{
Call.sendMessage(con.id, text, fromName, from);
Call.sendMessage(con, text, fromName, from);
}
}
@@ -848,7 +836,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
}else if(spawner != null && spawner.isValid()){
spawner.updateSpawning(this);
}else if(!netServer.isWaitingForPlayers()){
if(!Net.client()){
if(!net.client()){
if(lastSpawner != null && lastSpawner.isValid()){
this.spawner = lastSpawner;
}else if(getClosestCore() != null){
@@ -920,7 +908,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
buffer.writeInt(Color.rgba8888(color));
buffer.writeByte(mech.id);
buffer.writeInt(mining == null ? noSpawner : mining.pos());
buffer.writeInt(spawner == null ? noSpawner : spawner.getTile().pos());
buffer.writeInt(spawner == null || !spawner.hasUnit(this) ? noSpawner : spawner.getTile().pos());
buffer.writeShort((short)(baseRotation * 2));
writeBuilding(buffer);

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.impl;
package io.anuke.mindustry.entities.type;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.mindustry.entities.traits.SolidTrait;

View File

@@ -8,8 +8,6 @@ import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.impl.BaseEntity;
import io.anuke.mindustry.entities.traits.HealthTrait;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.game.*;
@@ -207,14 +205,12 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
if(other == null) continue;
if(other.entity == null || !(other.interactable(tile.getTeam()))) continue;
other.block().onProximityUpdate(other);
tmpTiles.add(other);
//add this tile to proximity of nearby tiles
if(!other.entity.proximity.contains(tile, true)){
other.entity.proximity.add(tile);
}
tmpTiles.add(other);
}
//using a set to prevent duplicates
@@ -224,6 +220,10 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
block.onProximityAdded(tile);
block.onProximityUpdate(tile);
for(Tile other : tmpTiles){
other.block().onProximityUpdate(other);
}
}
public Array<Tile> proximity(){

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.impl;
package io.anuke.mindustry.entities.type;
import io.anuke.arc.util.pooling.Pool.Poolable;
import io.anuke.mindustry.entities.traits.ScaleTrait;

View File

@@ -2,6 +2,7 @@ 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.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
@@ -11,7 +12,6 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.impl.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.EventType.*;
@@ -19,9 +19,9 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
@@ -78,7 +78,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
@Override
public void damage(float amount){
if(!Net.client()){
if(!net.client()){
super.damage(calculateDamage(amount));
}
hitTime = hitDuration;
@@ -110,6 +110,10 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
drownTime = 0f;
status.clear();
Events.fire(new UnitDestroyEvent(this));
if(explosiveness > 7f && this == player){
Events.fire(Trigger.suicideBomb);
}
}
@Override
@@ -209,15 +213,25 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
float radScl = 1.5f;
float fsize = getSize() / radScl;
moveVector.setZero();
float cx = x - fsize/2f, cy = y - fsize/2f;
Units.nearby(x - fsize/2f, y - fsize/2f, fsize, fsize, en -> {
if(en == this || en.isFlying() != isFlying()) return;
for(Team team : Team.all){
avoid(unitGroups[team.ordinal()].intersect(cx, cy, fsize, fsize));
}
avoid(playerGroup.intersect(cx, cy, fsize, fsize));
velocity.add(moveVector.x / mass() * Time.delta(), moveVector.y / mass() * Time.delta());
}
private void avoid(Array<? extends Unit> arr){
float radScl = 1.5f;
for(Unit en : arr){
if(en.isFlying() != isFlying()) continue;
float dst = dst(en);
float scl = Mathf.clamp(1f - dst / (getSize()/(radScl*2f) + en.getSize()/(radScl*2f)));
moveVector.add(Tmp.v1.set((x - en.x) * scl, (y - en.y) * scl).limit(0.4f));
});
velocity.add(moveVector.x / mass() * Time.delta(), moveVector.y / mass() * Time.delta());
}
}
public @Nullable TileEntity getClosestCore(){
@@ -308,8 +322,11 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
drownTime = Mathf.clamp(drownTime);
if(drownTime >= 0.999f && !Net.client()){
if(drownTime >= 0.999f && !net.client()){
damage(health + 1);
if(this == player){
Events.fire(Trigger.drown);
}
}
float px = x, py = y;
@@ -347,7 +364,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
public void applyEffect(StatusEffect effect, float duration){
if(dead || Net.client()) return; //effects are synced and thus not applied through clients
if(dead || net.client()) return; //effects are synced and thus not applied through clients
status.handleApply(this, effect, duration);
}
@@ -372,7 +389,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
public void drawStats(){
Draw.color(Color.BLACK, team.color, healthf() + Mathf.absin(Time.time(), Math.max(healthf() * 5f, 1f), 1f - healthf()));
Draw.color(Color.black, team.color, healthf() + Mathf.absin(Time.time(), Math.max(healthf() * 5f, 1f), 1f - healthf()));
Draw.rect(getPowerCellRegion(), x, y, rotation - 90);
Draw.color();
@@ -400,10 +417,10 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
(3f + Mathf.absin(Time.time(), 5f, 1f)) * itemtime);
if(number){
Core.scene.skin.getFont("outline").draw(item.amount + "",
Fonts.outline.draw(item.amount + "",
x + Angles.trnsx(rotation + 180f, backTrns),
y + Angles.trnsy(rotation + 180f, backTrns) - 3,
Pal.accent, 0.25f * itemtime / UnitScl.dp.scl(1f), false, Align.center
Pal.accent, 0.25f * itemtime / Scl.scl(1f), false, Align.center
);
}
}

View File

@@ -1,16 +1,14 @@
package io.anuke.mindustry.entities.units;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.Bits;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.entities.traits.Saveable;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.type.StatusEffect;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.type.*;
import java.io.*;
@@ -28,8 +26,8 @@ public class Statuses implements Saveable{
private float damageMultiplier;
private float armorMultiplier;
public void handleApply(io.anuke.mindustry.entities.type.Unit unit, StatusEffect effect, float duration){
if(effect == StatusEffects.none || unit.isImmune(effect)) return; //don't apply empty or immune effects
public void handleApply(Unit unit, StatusEffect effect, float duration){
if(effect == StatusEffects.none || effect == null || unit.isImmune(effect)) return; //don't apply empty or immune effects
if(statuses.size > 0){
//check for opposite effects
@@ -39,6 +37,7 @@ public class Statuses implements Saveable{
entry.time = Math.max(entry.time, duration);
return;
}else if(entry.effect.reactsWith(effect)){ //find opposite
globalResult.effect = entry.effect;
entry.effect.getTransition(unit, effect, entry.time, duration, globalResult);
entry.time = globalResult.time;
@@ -60,7 +59,7 @@ public class Statuses implements Saveable{
public Color getStatusColor(){
if(statuses.size == 0){
return Tmp.c1.set(Color.WHITE);
return Tmp.c1.set(Color.white);
}
float r = 0f, g = 0f, b = 0f;

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.entities.units;
import io.anuke.arc.*;
public enum UnitCommand{
attack, retreat, patrol;
attack, retreat, rally;
private final String localized;
public static final UnitCommand[] all = values();

View File

@@ -1,14 +1,64 @@
package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.*;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.type.Zone;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.entities.type.Player;
public class EventType{
//events that occur very often
public enum Trigger{
shock,
phaseDeflectHit,
impactPower,
thoriumReactorOverheat,
itemLaunch,
fireExtinguish,
newGame,
tutorialComplete,
flameAmmo,
turretCool,
enablePixelation,
drown,
exclusionDeath,
suicideBomb,
openWiki
}
public static class WinEvent{}
public static class LoseEvent{}
public static class LaunchEvent{}
public static class MapMakeEvent{}
public static class MapPublishEvent{}
public static class CommandIssueEvent{
public final Tile tile;
public final UnitCommand command;
public CommandIssueEvent(Tile tile, UnitCommand command){
this.tile = tile;
this.command = command;
}
}
public static class PlayerChatEvent{
public final Player player;
public final String message;
public PlayerChatEvent(Player player, String message){
this.player = player;
this.message = message;
}
}
/** Called when a zone's requirements are met. */
public static class ZoneRequireCompleteEvent{
public final Zone zone, required;
@@ -118,6 +168,14 @@ public class EventType{
}
}
public static class ResearchEvent{
public final UnlockableContent content;
public ResearchEvent(UnlockableContent content){
this.content = content;
}
}
/**
* Called when block building begins by placing down the BuildBlock.
* The tile's block will nearly always be a BuildBlock.
@@ -137,11 +195,13 @@ public class EventType{
public static class BlockBuildEndEvent{
public final Tile tile;
public final Team team;
public final @Nullable Player player;
public final boolean breaking;
public BlockBuildEndEvent(Tile tile, Team team, boolean breaking){
public BlockBuildEndEvent(Tile tile, @Nullable Player player, Team team, boolean breaking){
this.tile = tile;
this.team = team;
this.player = player;
this.breaking = breaking;
}
}
@@ -182,10 +242,29 @@ public class EventType{
}
}
public static class UnitCreateEvent{
public final BaseUnit unit;
public UnitCreateEvent(BaseUnit unit){
this.unit = unit;
}
}
public static class ResizeEvent{
}
public static class MechChangeEvent{
public final Player player;
public final Mech mech;
public MechChangeEvent(Player player, Mech mech){
this.player = player;
this.mech = mech;
}
}
/** Called after connecting; when a player recieves world data and is ready to play.*/
public static class PlayerJoin{
public final Player player;
@@ -193,7 +272,16 @@ public class EventType{
this.player = player;
}
}
/** Called when a player connects, but has not joined the game yet.*/
public static class PlayerConnect{
public final Player player;
public PlayerConnect(Player player){
this.player = player;
}
}
public static class PlayerLeave{
public final Player player;

View File

@@ -22,7 +22,6 @@ public enum Gamemode{
attack(rules -> {
rules.unitDrops = true;
rules.attackMode = true;
rules.waves = true;
}, map -> map.teams.contains(waveTeam.ordinal())),
pvp(rules -> {
rules.pvp = true;
@@ -69,6 +68,20 @@ public enum Gamemode{
this.validator = validator;
}
public static Gamemode bestFit(Rules rules){
if(rules.pvp){
return pvp;
}else if(rules.editor){
return editor;
}else if(rules.attackMode){
return attack;
}else if(rules.infiniteResources){
return sandbox;
}else{
return survival;
}
}
/** Applies this preset to this ruleset. */
public Rules apply(Rules in){
rules.accept(in);

View File

@@ -52,10 +52,12 @@ public class GlobalData{
}
public void importData(FileHandle file){
FileHandle zipped = new ZipFileHandle(file);
FileHandle dest = Core.files.local("zipdata.zip");
file.copyTo(dest);
FileHandle zipped = new ZipFileHandle(dest);
FileHandle base = Core.settings.getDataDirectory();
if(!base.child("settings.bin").exists()){
if(!zipped.child("settings.bin").exists()){
throw new IllegalArgumentException("Not valid save data.");
}
@@ -63,12 +65,13 @@ public class GlobalData{
for(FileHandle f : base.list()){
if(f.isDirectory()){
f.deleteDirectory();
}else{
}else if(!f.name().equals("zipdata.zip")){
f.delete();
}
}
zipped.walk(f -> f.copyTo(base.child(f.path())));
dest.delete();
}
public void modified(){

View File

@@ -133,8 +133,8 @@ public class MusicControl{
}
/** Plays a music track once and only once. If something is already playing, does nothing.*/
private void playOnce(@NonNull Music music){
if(current != null) return; //do not interrupt already-playing tracks
private void playOnce(Music music){
if(current != null || music == null) return; //do not interrupt already-playing tracks
//save last random track played to prevent duplicates
lastRandomPlayed = music;

View File

@@ -74,4 +74,9 @@ public class Rules{
public Rules copy(){
return JsonIO.copy(this);
}
/** Returns the gamemode that best fits these rules.*/
public Gamemode mode(){
return Gamemode.bestFit(this);
}
}

View File

@@ -266,6 +266,10 @@ public class Saves{
return meta == null || meta.rules == null ? null : meta.rules.zone;
}
public Gamemode mode(){
return Gamemode.bestFit(meta.rules);
}
public int getBuild(){
return meta.build;
}
@@ -293,9 +297,6 @@ public class Saves{
public void exportFile(FileHandle file) throws IOException{
try{
if(!file.extension().equals(saveExtension)){
file = file.parent().child(file.nameWithoutExtension() + "." + saveExtension);
}
SaveIO.fileFor(index).copyTo(file);
}catch(Exception e){
throw new IOException(e);

View File

@@ -10,7 +10,7 @@ public enum Team{
crux(Color.valueOf("e82d2d")),
green(Color.valueOf("4dd98b")),
purple(Color.valueOf("9a4bdf")),
blue(Color.ROYAL.cpy());
blue(Color.royal.cpy());
public final static Team[] all = values();
public final Color color;

View File

@@ -115,9 +115,7 @@ public class Tutorial{
outline("blockinfo");
}
},
conveyor(
line -> Strings.format(line, Math.min(placed(Blocks.conveyor), 2), 2),
() -> placed(Blocks.conveyor, 2) && event("lineconfirm") && event("coreitem")){
conveyor(() -> placed(Blocks.conveyor, 2) && event("lineconfirm") && event("coreitem")){
void draw(){
outline("category-distribution");
outline("block-conveyor");
@@ -179,6 +177,7 @@ public class Tutorial{
state.wave = 5;
//end tutorial, never show it again
Events.fire(Trigger.tutorialComplete);
Core.settings.put("playedtutorial", true);
Core.settings.save();
}
@@ -188,15 +187,14 @@ public class Tutorial{
}
},;
protected final String line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
protected String line = "";
protected final Function<String, String> text;
protected final Array<String> sentences;
protected Array<String> sentences;
protected final BooleanProvider done;
TutorialStage(Function<String, String> text, BooleanProvider done){
this.text = text;
this.done = done;
this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
}
TutorialStage(BooleanProvider done){
@@ -205,6 +203,10 @@ public class Tutorial{
/** displayed tutorial stage text.*/
public String text(){
if(sentences == null){
this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
}
String line = sentences.get(control.tutorial.sentence);
return line.contains("{") ? text.get(line) : line;
}
@@ -273,20 +275,20 @@ public class Tutorial{
Element element = Core.scene.findVisible(name);
if(element != null && !toggled(name)){
element.localToStageCoordinates(Tmp.v1.setZero());
float sin = Mathf.sin(11f, UnitScl.dp.scl(4f));
Lines.stroke(UnitScl.dp.scl(7f), Pal.place);
float sin = Mathf.sin(11f, Scl.scl(4f));
Lines.stroke(Scl.scl(7f), Pal.place);
Lines.rect(Tmp.v1.x - sin, Tmp.v1.y - sin, element.getWidth() + sin*2, element.getHeight() + sin*2);
float size = Math.max(element.getWidth(), element.getHeight()) + Mathf.absin(11f/2f, UnitScl.dp.scl(18f));
float size = Math.max(element.getWidth(), element.getHeight()) + Mathf.absin(11f/2f, Scl.scl(18f));
float angle = Angles.angle(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, Tmp.v1.x + element.getWidth()/2f, Tmp.v1.y + element.getHeight()/2f);
Tmp.v2.trns(angle + 180f, size*1.4f);
float fs = UnitScl.dp.scl(40f);
float fs2 = UnitScl.dp.scl(56f);
float fs = Scl.scl(40f);
float fs2 = Scl.scl(56f);
Draw.color(Pal.gray);
Drawf.tri(Tmp.v1.x + element.getWidth()/2f + Tmp.v2.x, Tmp.v1.y + element.getHeight()/2f + Tmp.v2.y, fs2, fs2, angle);
Draw.color(Pal.place);
Tmp.v2.setLength(Tmp.v2.len() - UnitScl.dp.scl(4));
Tmp.v2.setLength(Tmp.v2.len() - Scl.scl(4));
Drawf.tri(Tmp.v1.x + element.getWidth()/2f + Tmp.v2.x, Tmp.v1.y + element.getHeight()/2f + Tmp.v2.y, fs, fs, angle);
Draw.reset();
}

View File

@@ -1,12 +1,13 @@
package io.anuke.mindustry.game;
import io.anuke.arc.Core;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.util.Strings;
import io.anuke.arc.util.io.PropertiesUtils;
import io.anuke.arc.*;
import io.anuke.arc.Files.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*;
import java.io.IOException;
import java.io.*;
public class Version{
/** Build type. 'official' for official releases; 'custom' or 'bleeding edge' are also used. */
@@ -26,13 +27,13 @@ public class Version{
if(!enabled) return;
try{
FileHandle file = Core.files.internal("version.properties");
FileHandle file = OS.isAndroid || OS.isIos ? Core.files.internal("version.properties") : new FileHandle("version.properties", FileType.Internal);
ObjectMap<String, String> map = new ObjectMap<>();
PropertiesUtils.load(map, file.reader());
type = map.get("type");
number = Integer.parseInt(map.get("number"));
number = Integer.parseInt(map.get("number", "4"));
modifier = map.get("modifier");
if(map.get("build").contains(".")){
String[] split = map.get("build").split("\\.");

View File

@@ -44,7 +44,7 @@ public class BlockRenderer implements Disposable{
shadows.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
shadows.resize(world.width(), world.height());
shadows.begin();
Core.graphics.clear(Color.WHITE);
Core.graphics.clear(Color.white);
Draw.proj().setOrtho(0, 0, shadows.getWidth(), shadows.getHeight());
Draw.color(shadowColor);
@@ -65,7 +65,7 @@ public class BlockRenderer implements Disposable{
fog.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
fog.resize(world.width(), world.height());
fog.begin();
Core.graphics.clear(Color.WHITE);
Core.graphics.clear(Color.white);
Draw.proj().setOrtho(0, 0, fog.getWidth(), fog.getHeight());
for(int x = 0; x < world.width(); x++){
@@ -129,10 +129,10 @@ public class BlockRenderer implements Disposable{
for(Tile tile : shadowEvents){
//clear it first
Draw.color(Color.WHITE);
Draw.color(Color.white);
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
//then draw the shadow
Draw.color(!tile.block().hasShadow ? Color.WHITE : shadowColor);
Draw.color(!tile.block().hasShadow ? Color.white : shadowColor);
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
}

View File

@@ -47,7 +47,7 @@ public enum CacheLayer{
renderer.blocks.floor.endc();
renderer.shieldBuffer.begin();
Core.graphics.clear(Color.CLEAR);
Core.graphics.clear(Color.clear);
renderer.blocks.floor.beginc();
}

View File

@@ -53,7 +53,7 @@ public class IndexedRenderer implements Disposable{
private Matrix3 projMatrix = new Matrix3();
private Matrix3 transMatrix = new Matrix3();
private Matrix3 combined = new Matrix3();
private float color = Color.WHITE.toFloatBits();
private float color = Color.white.toFloatBits();
public IndexedRenderer(int sprites){
resize(sprites);

View File

@@ -8,7 +8,7 @@ import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.glutils.FrameBuffer;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.UnitScl;
import io.anuke.arc.scene.ui.layout.Scl;
import io.anuke.arc.util.*;
import io.anuke.arc.util.noise.RidgedPerlin;
import io.anuke.arc.util.noise.Simplex;
@@ -168,8 +168,8 @@ public class MenuRenderer implements Disposable{
//draw shadows
Draw.proj().setOrtho(0, 0, shadows.getWidth(), shadows.getHeight());
shadows.beginDraw(Color.CLEAR);
Draw.color(Color.BLACK);
shadows.beginDraw(Color.clear);
Draw.color(Color.black);
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
if(world.rawTile(x, y).block() != Blocks.air){
@@ -220,7 +220,7 @@ public class MenuRenderer implements Disposable{
public void render(){
time += Time.delta();
float scaling = Math.max(UnitScl.dp.scl(4f), Math.max(Core.graphics.getWidth() / ((width - 1f) * tilesize), Core.graphics.getHeight() / ((height - 1f) * tilesize)));
float scaling = Math.max(Scl.scl(4f), Math.max(Core.graphics.getWidth() / ((width - 1f) * tilesize), Core.graphics.getHeight() / ((height - 1f) * tilesize)));
camera.position.set(width * tilesize / 2f, height * tilesize / 2f);
camera.resize(Core.graphics.getWidth() / scaling,
Core.graphics.getHeight() / scaling);
@@ -270,7 +270,7 @@ public class MenuRenderer implements Disposable{
Fill.circle(x + Angles.trnsx(rotation + 180, engineOffset), y + Angles.trnsy(rotation + 180, engineOffset),
engineSize + Mathf.absin(Time.time(), 2f, engineSize / 4f));
Draw.color(Color.WHITE);
Draw.color(Color.white);
Fill.circle(x + Angles.trnsx(rotation + 180, engineOffset - 1f), y + Angles.trnsy(rotation + 180, engineOffset - 1f),
(engineSize + Mathf.absin(Time.time(), 2f, engineSize / 4f)) / 2f);
Draw.color();

View File

@@ -82,7 +82,7 @@ public class MinimapRenderer implements Disposable{
for(Unit unit : units){
float rx = (unit.x - rect.x) / rect.width * w, ry = (unit.y - rect.y) / rect.width * h;
Draw.color(unit.getTeam().color);
Fill.rect(x + rx, y + ry, UnitScl.dp.scl(baseSize / 2f), UnitScl.dp.scl(baseSize / 2f));
Fill.rect(x + rx, y + ry, Scl.scl(baseSize / 2f), Scl.scl(baseSize / 2f));
}
Draw.color();

View File

@@ -84,7 +84,7 @@ public class OverlayRenderer{
for(Tile core : state.teams.get(enemy).cores){
float dst = Mathf.dst(player.x, player.y, core.drawx(), core.drawy());
if(dst < state.rules.enemyCoreBuildRadius * 1.5f){
Draw.color(Color.DARK_GRAY);
Draw.color(Color.darkGray);
Lines.circle(core.drawx(), core.drawy() - 2, state.rules.enemyCoreBuildRadius);
Draw.color(Pal.accent, enemy.color, 0.5f + Mathf.absin(Time.time(), 10f, 0.5f));
Lines.circle(core.drawx(), core.drawy(), state.rules.enemyCoreBuildRadius);
@@ -94,7 +94,7 @@ public class OverlayRenderer{
}
Lines.stroke(2f);
Draw.color(Color.GRAY, Color.LIGHT_GRAY, Mathf.absin(Time.time(), 8f, 1f));
Draw.color(Color.gray, Color.lightGray, Mathf.absin(Time.time(), 8f, 1f));
for(Tile tile : spawner.getGroundSpawns()){
if(tile.withinDst(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){

View File

@@ -51,7 +51,7 @@ public class Pal{
health = Color.valueOf("ff341c"),
heal = Color.valueOf("98ffa9"),
bar = Color.SLATE,
bar = Color.slate,
accent = Color.valueOf("ffd37f"),
stat = Color.valueOf("ffd37f"),
gray = Color.valueOf("454545"),

View File

@@ -37,8 +37,8 @@ public class Pixelator implements Disposable{
float px = Core.camera.position.x, py = Core.camera.position.y;
Core.camera.position.set((int)px + ((int)(camera.width) % 2 == 0 ? 0 : 0.5f), (int)py + ((int)(camera.height) % 2 == 0 ? 0 : 0.5f));
int w = (int)(Core.camera.width);
int h = (int)(Core.camera.height);
int w = (int)(Core.camera.width * renderer.landScale());
int h = (int)(Core.camera.height * renderer.landScale());
if(!graphics.isHidden() && (buffer.getWidth() != w || buffer.getHeight() != h)){
buffer.resize(w, h);

View File

@@ -5,7 +5,7 @@ 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.UnitScl;
import io.anuke.arc.scene.ui.layout.Scl;
import io.anuke.arc.util.Time;
public class Shaders{
@@ -48,7 +48,7 @@ public class Shaders{
setUniformf("u_resolution", Core.graphics.getWidth(), Core.graphics.getHeight());
setUniformi("u_time", (int)(time += Core.graphics.getDeltaTime() * 60f));
setUniformf("u_uv", Core.atlas.white().getU(), Core.atlas.white().getV());
setUniformf("u_scl", UnitScl.dp.scl(1f));
setUniformf("u_scl", Scl.scl(1f));
setUniformf("u_uv2", Core.atlas.white().getU2(), Core.atlas.white().getV2());
}
}
@@ -124,8 +124,8 @@ public class Shaders{
@Override
public void apply(){
setUniformf("u_dp", UnitScl.dp.scl(1f));
setUniformf("u_time", Time.time() / UnitScl.dp.scl(1f));
setUniformf("u_dp", Scl.scl(1f));
setUniformf("u_time", Time.time() / Scl.scl(1f));
setUniformf("u_offset",
Core.camera.position.x - Core.camera.width / 2,
Core.camera.position.y - Core.camera.height / 2);

View File

@@ -123,7 +123,7 @@ public class DesktopInput extends InputHandler{
@Override
public void update(){
if(Net.active() && Core.input.keyTap(Binding.player_list)){
if(net.active() && Core.input.keyTap(Binding.player_list)){
ui.listfrag.toggle();
}
@@ -236,6 +236,15 @@ public class DesktopInput extends InputHandler{
selectY = tileY(Core.input.mouseY());
}
if (mode == placing && block != null){
if (!overrideLineRotation && !Core.input.keyDown(Binding.diagonal_placement) && (selectX != cursorX || selectY != cursorY) && ((int) Core.input.axisTap(Binding.rotate) != 0)){
rotation = ((int)((Angles.angle(selectX, selectY, cursorX, cursorY) + 45) / 90f)) % 4;
overrideLineRotation = true;
}
}else{
overrideLineRotation = false;
}
if(Core.input.keyRelease(Binding.break_block) || Core.input.keyRelease(Binding.select)){
if(mode == placing && block != null){ //touch up while placing, place everything in selection
@@ -280,7 +289,7 @@ public class DesktopInput extends InputHandler{
}
@Override
public void updateController(){
public void updateState(){
if(state.is(State.menu)){
droppingItem = false;
mode = none;

View File

@@ -19,7 +19,6 @@ import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.fragments.*;
@@ -39,6 +38,7 @@ public abstract class InputHandler implements InputProcessor{
public final OverlayFragment frag = new OverlayFragment();
public Block block;
public boolean overrideLineRotation;
public int rotation;
public boolean droppingItem;
@@ -49,21 +49,22 @@ public abstract class InputHandler implements InputProcessor{
@Remote(targets = Loc.client, called = Loc.server)
public static void dropItem(Player player, float angle){
if(Net.server() && player.item().amount <= 0){
if(net.server() && player.item().amount <= 0){
throw new ValidateException(player, "Player cannot drop an item.");
}
Effects.effect(Fx.dropItem, Color.WHITE, player.x, player.y, angle, player.item().item);
Effects.effect(Fx.dropItem, Color.white, player.x, player.y, angle, player.item().item);
player.clearItem();
}
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Tile tile){
if(Net.server() && (player.item().amount <= 0 || player.isTransferring || !player.timer.get(Player.timerTransfer, 40))){
if(player == null || player.timer == null || !player.timer.get(Player.timerTransfer, 40)) return;
if(net.server() && (player.item().amount <= 0 || player.isTransferring|| !tile.interactable(player.getTeam()))){
throw new ValidateException(player, "Player cannot transfer an item.");
}
if(player == null || tile.entity == null) return;
if(tile.entity == null) return;
player.isTransferring = true;
@@ -132,7 +133,7 @@ public abstract class InputHandler implements InputProcessor{
}
public void updateController(){
public void updateState(){
}
@@ -273,6 +274,23 @@ public abstract class InputHandler implements InputProcessor{
public void remove(){
Core.input.removeProcessor(this);
frag.remove();
if(Core.scene != null){
Table table = (Table)Core.scene.find("inputTable");
if(table != null){
table.clear();
}
}
}
public void add(){
Core.input.addProcessor(this);
if(Core.scene != null){
Table table = (Table)Core.scene.find("inputTable");
if(table != null){
table.clear();
buildUI(table);
}
}
}
public boolean canShoot(){
@@ -297,7 +315,7 @@ public abstract class InputHandler implements InputProcessor{
ItemStack stack = player.item();
if(tile.block().acceptStack(stack.item, stack.amount, tile, player) > 0 && tile.interactable(player.getTeam()) && tile.block().hasItems){
if(tile.block().acceptStack(stack.item, stack.amount, tile, player) > 0 && tile.interactable(player.getTeam()) && tile.block().hasItems && player.item().amount > 0 && !player.isTransferring && tile.interactable(player.getTeam())){
Call.transferInventory(player, tile);
}else{
Call.dropItem(player.angleTo(x, y));
@@ -363,7 +381,10 @@ public abstract class InputHandler implements InputProcessor{
}
float angle = Angles.angle(startX, startY, endX, endY);
int baseRotation = (startX == endX && startY == endY) ? rotation : ((int)((angle + 45) / 90f)) % 4;
int baseRotation = rotation;
if (!overrideLineRotation || diagonal){
baseRotation = (startX == endX && startY == endY) ? rotation : ((int)((angle + 45) / 90f)) % 4;
}
Tmp.r3.set(-1, -1, 0, 0);
@@ -377,7 +398,11 @@ public abstract class InputHandler implements InputProcessor{
Point2 next = i == points.size - 1 ? null : points.get(i + 1);
line.x = point.x;
line.y = point.y;
line.rotation = next != null ? Tile.relativeTo(point.x, point.y, next.x, next.y) : baseRotation;
if (!overrideLineRotation || diagonal){
line.rotation = next != null ? Tile.relativeTo(point.x, point.y, next.x, next.y) : baseRotation;
}else{
line.rotation = rotation;
}
line.last = next == null;
cons.accept(line);

View File

@@ -17,8 +17,10 @@ import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.PlaceUtils.*;
import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.*;
@@ -29,11 +31,12 @@ public class MobileInput extends InputHandler implements GestureListener{
private static final float maxPanSpeed = 1.3f;
private static Rectangle r1 = new Rectangle(), r2 = new Rectangle();
/** Distance to edge of screen to start panning. */
private final float edgePan = UnitScl.dp.scl(60f);
private final float edgePan = Scl.scl(60f);
//gesture data
private Vector2 vector = new Vector2();
private float lastZoom = -1;
private GestureDetector detector;
/** Position where the player started dragging a line. */
private int lineStartX, lineStartY;
@@ -63,12 +66,6 @@ public class MobileInput extends InputHandler implements GestureListener{
private int prevX, prevY, prevRotation;
public MobileInput(){
Events.on(ClientLoadEvent.class, e -> {
Core.input.addProcessor(new GestureDetector(20, 0.5f, 0.4f, 0.15f, this));
});
}
//region utility methods
/** Check and assign targets for a specific position. */
@@ -190,7 +187,7 @@ public class MobileInput extends InputHandler implements GestureListener{
TextureRegion region = placeDraw.region;
Draw.mixcol(Pal.accent, Mathf.clamp((1f - request.scale) / 0.5f + 0.12f + Mathf.absin(Time.time(), 8f, 0.35f)));
Draw.tint(Color.WHITE, Pal.breakInvalid, request.redness);
Draw.tint(Color.white, Pal.breakInvalid, request.redness);
Draw.rect(region, tile.worldx() + offset, tile.worldy() + offset,
region.getWidth() * request.scale * Draw.scl * placeDraw.scalex,
@@ -249,27 +246,27 @@ public class MobileInput extends InputHandler implements GestureListener{
@Override
public void buildUI(Table table){
table.addImage("whiteui").color(Pal.gray).height(4f).colspan(4).growX();
table.addImage().color(Pal.gray).height(4f).colspan(4).growX();
table.row();
table.left().margin(0f).defaults().size(48f);
table.addImageButton("icon-break-small", "clear-toggle-partial", iconsizesmall, () -> {
table.addImageButton(Icon.breakSmall, Styles.clearTogglePartiali, () -> {
mode = mode == breaking ? block == null ? none : placing : breaking;
lastBlock = block;
}).update(l -> l.setChecked(mode == breaking)).name("breakmode");
//diagonal swap button
table.addImageButton("icon-diagonal-small", "clear-toggle-partial", iconsizesmall, () -> {
table.addImageButton(Icon.diagonalSmall, Styles.clearTogglePartiali, () -> {
Core.settings.put("swapdiagonal", !Core.settings.getBool("swapdiagonal"));
Core.settings.save();
}).update(l -> l.setChecked(Core.settings.getBool("swapdiagonal")));
//rotate button
table.addImageButton("icon-arrow-small", "clear-partial", iconsizesmall, () -> rotation = Mathf.mod(rotation + 1, 4))
table.addImageButton(Icon.arrowSmall, Styles.clearPartiali,() -> rotation = Mathf.mod(rotation + 1, 4))
.update(i -> i.getImage().setRotationOrigin(rotation * 90, Align.center)).visible(() -> block != null && block.rotate);
//confirm button
table.addImageButton("icon-check-small", "clear-partial", iconsizesmall, () -> {
table.addImageButton(Icon.checkSmall, Styles.clearPartiali, () -> {
for(PlaceRequest request : selection){
Tile tile = request.tile();
@@ -294,8 +291,9 @@ public class MobileInput extends InputHandler implements GestureListener{
}).visible(() -> !selection.isEmpty()).name("confirmplace");
Core.scene.table(t -> {
t.setName("cancelMobile");
t.bottom().left().visible(() -> (player.isBuilding() || block != null || mode == breaking) && !state.is(State.menu));
t.addImageTextButton("$cancel", "icon-cancel", 16*2, () -> {
t.addImageTextButton("$cancel", Icon.cancelSmall, () -> {
player.clearBuilding();
mode = none;
block = null;
@@ -443,6 +441,24 @@ public class MobileInput extends InputHandler implements GestureListener{
//endregion
//region input events
@Override
public void add(){
Core.input.addProcessor(detector = new GestureDetector(20, 0.5f, 0.4f, 0.15f, this));
super.add();
}
@Override
public void remove(){
super.remove();
if(detector != null){
Core.input.removeProcessor(detector);
}
if(Core.scene != null && Core.scene.find("cancelMobile") != null){
Core.scene.find("cancelMobile").remove();
}
}
@Override
public boolean touchDown(int screenX, int screenY, int pointer, KeyCode button){
if(state.is(State.menu) || player.isDead()) return false;

View File

@@ -207,7 +207,7 @@ public class LegacyMapIO{
if(block.ore != null) tile.setOverlay(block.ore);
//place core
if(color == Color.rgba8888(Color.GREEN)){
if(color == Color.rgba8888(Color.green)){
for(int dx = 0; dx < 3; dx++){
for(int dy = 0; dy < 3; dy++){
int worldx = dx - 1 + x;

View File

@@ -73,7 +73,7 @@ public class MapIO{
Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888);
Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888);
int black = Color.rgba8888(Color.BLACK);
int black = Color.rgba8888(Color.black);
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
CachedTile tile = new CachedTile(){
@Override

View File

@@ -5,7 +5,7 @@ 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.bullet.Bullet;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.ShooterTrait;

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.io.versions;
import io.anuke.arc.function.Supplier;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.type.Bullet;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.base.*;

View File

@@ -20,6 +20,8 @@ public class Map implements Comparable<Map>{
public final FileHandle file;
/** Format version. */
public final int version;
/** Whether this map is managed, e.g. downloaded from the Steam workshop.*/
public boolean workshop;
/** Map width/height, shorts. */
public int width, height;
/** Preview texture. */
@@ -57,8 +59,12 @@ public class Map implements Comparable<Map>{
return Core.settings.getInt("hiscore" + file.nameWithoutExtension(), 0);
}
public Texture safeTexture(){
return texture == null ? Core.assets.get("sprites/error.png") : texture;
}
public FileHandle previewFile(){
return Vars.mapPreviewDirectory.child(file.nameWithoutExtension() + ".png");
return Vars.mapPreviewDirectory.child((workshop ? file.parent().name() : file.nameWithoutExtension()) + ".png");
}
public FileHandle cacheFile(){
@@ -127,6 +133,8 @@ public class Map implements Comparable<Map>{
@Override
public int compareTo(Map map){
int work = -Boolean.compare(workshop, map.workshop);
if(work != 0) return work;
int type = -Boolean.compare(custom, map.custom);
if(type != 0) return type;
int modes = Boolean.compare(Gamemode.pvp.valid(this), Gamemode.pvp.valid(map));

View File

@@ -5,6 +5,8 @@ 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.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.*;
@@ -19,12 +21,27 @@ public class MapPreviewLoader extends TextureLoader{
try{
super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter);
}catch(Exception e){
e.printStackTrace();
Log.err(e);
MapPreviewParameter param = (MapPreviewParameter)parameter;
Vars.maps.queueNewPreview(param.map);
}
}
@Override
public Texture loadSync(AssetManager manager, String fileName, FileHandle file, TextureParameter parameter){
try{
return super.loadSync(manager, fileName, file, parameter);
}catch(Throwable e){
Log.err(e);
try{
return new Texture(file);
}catch(Throwable e2){
Log.err(e2);
return new Texture("sprites/error.png");
}
}
}
@Override
public Array<AssetDescriptor> getDependencies(String fileName, FileHandle file, TextureParameter parameter){
return Array.with(new AssetDescriptor<>("contentcreate", Content.class));

View File

@@ -81,6 +81,7 @@ public class Maps{
/** Load all maps. Should be called at application start. */
public void load(){
//defaults; must work
try{
for(String name : defaultMapNames){
FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension);
@@ -90,7 +91,27 @@ public class Maps{
throw new RuntimeException(e);
}
loadCustomMaps();
//custom
for(FileHandle file : customMapDirectory.list()){
try{
if(file.extension().equalsIgnoreCase(mapExtension)){
loadMap(file, true);
}
}catch(Exception e){
Log.err("Failed to load custom map file '{0}'!", file);
Log.err(e);
}
}
//workshop
for(FileHandle file : platform.getExternalMaps()){
try{
loadMap(file, false).workshop = true;
}catch(Exception e){
Log.err("Failed to load workshop map file '{0}'!", file);
Log.err(e);
}
}
}
public void reload(){
@@ -108,7 +129,7 @@ public class Maps{
* Save a custom map to the directory. This updates all values and stored data necessary.
* The tags are copied to prevent mutation later.
*/
public void saveMap(ObjectMap<String, String> baseTags){
public Map saveMap(ObjectMap<String, String> baseTags){
try{
StringMap tags = new StringMap(baseTags);
@@ -166,25 +187,35 @@ public class Maps{
}
maps.add(map);
maps.sort();
return map;
}catch(IOException e){
throw new RuntimeException(e);
}
}
/** Creates a legacy map by converting it to a non-legacy map and pasting it in a temp directory.
* Should be followed up by {@link #importMap(FileHandle)} .*/
public Map makeLegacyMap(FileHandle file) throws IOException{
FileHandle dst = tmpDirectory.child("conversion_map." + mapExtension);
LegacyMapIO.convertMap(file, dst);
return MapIO.createMap(dst, true);
}
/** Import a map, then save it. This updates all values and stored data necessary. */
public void importMap(FileHandle file) throws IOException{
FileHandle dest = findFile();
file.copyTo(dest);
createNewPreview(loadMap(dest, true), true);
Map map = loadMap(dest, true);
Exception[] error = {null};
createNewPreview(map, e -> {
maps.remove(map);
try{
map.file.delete();
}catch(Throwable ignored){
}
error[0] = e;
});
if(error[0] != null){
throw new IOException(error[0]);
}
}
/** Attempts to run the following code;
@@ -196,11 +227,11 @@ public class Maps{
Log.err(e);
if("Outdated legacy map format".equals(e.getMessage())){
ui.showError("$editor.errorlegacy");
ui.showErrorMessage("$editor.errornot");
}else if(e.getMessage() != null && e.getMessage().contains("Incorrect header!")){
ui.showError("$editor.errorheader");
ui.showErrorMessage("$editor.errorheader");
}else{
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, true)));
ui.showException("$editor.errorload", e);
}
}
}
@@ -290,40 +321,12 @@ public class Maps{
return str == null ? null : str.equals("[]") ? new Array<>() : Array.with(json.fromJson(SpawnGroup[].class, str));
}
public void loadLegacyMaps(){
boolean convertedAny = false;
for(FileHandle file : customMapDirectory.list()){
if(file.extension().equalsIgnoreCase(oldMapExtension)){
try{
convertedAny = true;
LegacyMapIO.convertMap(file, file.sibling(file.nameWithoutExtension() + "." + mapExtension));
//delete old, converted file; it is no longer useful
file.delete();
Log.info("Converted file {0}", file);
}catch(Exception e){
//rename the file to a 'mmap_conversion_failed' extension to keep it there just in case
//but don't delete it
file.copyTo(file.sibling(file.name() + "_conversion_failed"));
file.delete();
Log.err(e);
}
}
}
//free up any potential memory that was used up during conversion
if(convertedAny){
world.createTiles(1, 1);
//reload maps to load the converted ones
reload();
}
}
public void loadPreviews(){
for(Map map : maps){
//try to load preview
if(map.previewFile().exists()){
//this may fail, but calls createNewPreview
//this may fail, but calls queueNewPreview
Core.assets.load(new AssetDescriptor<>(map.previewFile().path() + "." + mapExtension, Texture.class, new MapPreviewParameter(map))).loaded = t -> map.texture = (Texture)t;
try{
@@ -341,7 +344,7 @@ public class Maps{
private void createAllPreviews(){
Core.app.post(() -> {
for(Map map : previewList){
createNewPreview(map, false);
createNewPreview(map, e -> Core.app.post(() -> map.texture = Core.assets.get("sprites/error.png")));
}
previewList.clear();
});
@@ -351,16 +354,12 @@ public class Maps{
Core.app.post(() -> previewList.add(map));
}
private void createNewPreview(Map map, boolean immediate){
private void createNewPreview(Map map, Consumer<Exception> failed){
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);
if(immediate){
map.texture = new Texture(pix);
}else{
Core.app.post(() -> map.texture = new Texture(pix));
}
map.texture = new Texture(pix);
executor.submit(() -> {
try{
map.previewFile().writePNG(pix);
@@ -369,9 +368,9 @@ public class Maps{
e.printStackTrace();
}
});
}catch(IOException e){
}catch(Exception e){
failed.accept(e);
Log.err("Failed to generate preview!", e);
Core.app.post(() -> map.texture = new Texture("sprites/error.png"));
}
}
@@ -420,16 +419,4 @@ public class Maps{
return map;
}
private void loadCustomMaps(){
for(FileHandle file : customMapDirectory.list()){
try{
if(file.extension().equalsIgnoreCase(mapExtension)){
loadMap(file, true);
}
}catch(Exception e){
Log.err("Failed to load custom map file '{0}'!", file);
Log.err(e);
}
}
}
}

View File

@@ -59,7 +59,7 @@ public class MirrorFilter extends GenerateFilter{
clamper.accept(Tmp.v1.trns(angle - 90, size).add(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()));
clamper.accept(Tmp.v2.set(Tmp.v1).sub(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()).rotate(180f).add(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()));
Lines.stroke(UnitScl.dp.scl(3f), Pal.accent);
Lines.stroke(Scl.scl(3f), Pal.accent);
Lines.line(Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y);
Draw.reset();
}

View File

@@ -21,9 +21,16 @@ public class Administration{
load();
}
public int getPlayerLimit(){
return Core.settings.getInt("playerlimit", 0);
}
public void setPlayerLimit(int limit){
Core.settings.putSave("playerlimit", limit);
}
public void setStrict(boolean on){
Core.settings.put("strict", on);
Core.settings.save();
Core.settings.putSave("strict", on);
}
public boolean getStrict(){

View File

@@ -0,0 +1,425 @@
package io.anuke.mindustry.net;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.net.*;
import io.anuke.arc.net.FrameworkMessage.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.async.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.Packets.*;
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.concurrent.*;
import static io.anuke.mindustry.Vars.*;
public class ArcNetImpl implements NetProvider{
final Client client;
final Supplier<DatagramPacket> packetSupplier = () -> new DatagramPacket(new byte[256], 256);
final Server server;
final CopyOnWriteArrayList<ArcConnection> connections = new CopyOnWriteArrayList<>();
Thread serverThread;
public ArcNetImpl(){
client = new Client(8192, 4096, new PacketSerializer());
client.setDiscoveryPacket(packetSupplier);
client.addListener(new NetListener(){
@Override
public void connected(Connection connection){
Connect c = new Connect();
c.addressTCP = connection.getRemoteAddressTCP().getAddress().getHostAddress();
if(connection.getRemoteAddressTCP() != null) c.addressTCP = connection.getRemoteAddressTCP().toString();
Core.app.post(() -> net.handleClientReceived(c));
}
@Override
public void disconnected(Connection connection, DcReason reason){
if(connection.getLastProtocolError() != null){
netClient.setQuiet();
}
Disconnect c = new Disconnect();
c.reason = reason.toString();
Core.app.post(() -> net.handleClientReceived(c));
}
@Override
public void received(Connection connection, Object object){
if(object instanceof FrameworkMessage) return;
Core.app.post(() -> {
try{
net.handleClientReceived(object);
}catch(Exception e){
handleException(e);
}
});
}
});
server = new Server(4096 * 2, 4096, new PacketSerializer());
server.setMulticast(multicastGroup, multicastPort);
server.setDiscoveryHandler((address, handler) -> {
ByteBuffer buffer = NetworkIO.writeServerData();
buffer.position(0);
handler.respond(buffer);
});
server.addListener(new NetListener(){
@Override
public void connected(Connection connection){
String ip = connection.getRemoteAddressTCP().getAddress().getHostAddress();
ArcConnection kn = new ArcConnection(ip, connection);
Connect c = new Connect();
c.addressTCP = ip;
Log.debug("&bRecieved connection: {0}", c.addressTCP);
connections.add(kn);
Core.app.post(() -> net.handleServerReceived(kn, c));
}
@Override
public void disconnected(Connection connection, DcReason reason){
ArcConnection k = getByArcID(connection.getID());
if(k == null) return;
Disconnect c = new Disconnect();
c.reason = reason.toString();
Core.app.post(() -> {
net.handleServerReceived(k, c);
connections.remove(k);
});
}
@Override
public void received(Connection connection, Object object){
ArcConnection k = getByArcID(connection.getID());
if(object instanceof FrameworkMessage || k == null) return;
Core.app.post(() -> {
try{
net.handleServerReceived(k, object);
}catch(RuntimeException e){
if(e.getCause() instanceof ValidateException){
ValidateException v = (ValidateException)e.getCause();
Log.err("Validation failed: {0} ({1})", v.player.name, v.getMessage());
}else{
e.printStackTrace();
}
}catch(Exception e){
e.printStackTrace();
}
});
}
});
}
private static boolean isLocal(InetAddress addr){
if(addr.isAnyLocalAddress() || addr.isLoopbackAddress()) return true;
try{
return NetworkInterface.getByInetAddress(addr) != null;
}catch(Exception e){
return false;
}
}
@Override
public void connectClient(String ip, int port, Runnable success){
Threads.daemon(() -> {
try{
//just in case
client.stop();
Threads.daemon("Net Client", () -> {
try{
client.run();
}catch(Exception e){
if(!(e instanceof ClosedSelectorException)) handleException(e);
}
});
client.connect(5000, ip, port, port);
success.run();
}catch(Exception e){
handleException(e);
}
});
}
@Override
public void disconnectClient(){
client.close();
}
@Override
public void sendClient(Object object, SendMode mode){
try{
if(mode == SendMode.tcp){
client.sendTCP(object);
}else{
client.sendUDP(object);
}
//sending things can cause an under/overflow, catch it and disconnect instead of crashing
}catch(BufferOverflowException | BufferUnderflowException e){
net.showError(e);
}
Pools.free(object);
}
@Override
public void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> invalid){
Threads.daemon(() -> {
try{
DatagramSocket socket = new DatagramSocket();
socket.send(new DatagramPacket(new byte[]{-2, 1}, 2, InetAddress.getByName(address), port));
socket.setSoTimeout(2000);
DatagramPacket packet = packetSupplier.get();
socket.receive(packet);
ByteBuffer buffer = ByteBuffer.wrap(packet.getData());
Host host = NetworkIO.readServerData(packet.getAddress().getHostAddress(), buffer);
Core.app.post(() -> valid.accept(host));
}catch(Exception e){
Core.app.post(() -> invalid.accept(e));
}
});
}
@Override
public void discoverServers(Consumer<Host> callback, Runnable done){
Array<InetAddress> foundAddresses = new Array<>();
client.discoverHosts(port, multicastGroup, multicastPort, 3000, packet -> {
Core.app.post(() -> {
try{
if(foundAddresses.contains(address -> address.equals(packet.getAddress()) || (isLocal(address) && isLocal(packet.getAddress())))){
return;
}
ByteBuffer buffer = ByteBuffer.wrap(packet.getData());
Host host = NetworkIO.readServerData(packet.getAddress().getHostAddress(), buffer);
callback.accept(host);
foundAddresses.add(packet.getAddress());
}catch(Exception e){
//don't crash when there's an error pinging a a server or parsing data
e.printStackTrace();
}
});
}, () -> Core.app.post(done));
}
@Override
public void dispose(){
disconnectClient();
closeServer();
try{
client.dispose();
}catch(IOException ignored){
}
}
@Override
public Iterable<ArcConnection> getConnections(){
return connections;
}
@Override
public void hostServer(int port) throws IOException{
connections.clear();
server.bind(port, port);
serverThread = new Thread(() -> {
try{
server.run();
}catch(Throwable e){
if(!(e instanceof ClosedSelectorException)) Threads.throwAppException(e);
}
}, "Net Server");
serverThread.setDaemon(true);
serverThread.start();
}
@Override
public void closeServer(){
connections.clear();
Threads.daemon(server::stop);
}
ArcConnection getByArcID(int id){
for(int i = 0; i < connections.size(); i++){
ArcConnection con = connections.get(i);
if(con.connection != null && con.connection.getID() == id){
return con;
}
}
return null;
}
private void handleException(Exception e){
if(e instanceof ArcNetException){
Core.app.post(() -> net.showError(new IOException("mismatch")));
}else if(e instanceof ClosedChannelException){
Core.app.post(() -> net.showError(new IOException("alreadyconnected")));
}else{
Core.app.post(() -> net.showError(e));
}
}
class ArcConnection extends NetConnection{
public final Connection connection;
public ArcConnection(String address, Connection connection){
super(address);
this.connection = connection;
}
@Override
public boolean isConnected(){
return connection.isConnected();
}
@Override
public void sendStream(Streamable stream){
connection.addListener(new InputStreamSender(stream.stream, 512){
int id;
@Override
protected void start(){
//send an object so the receiving side knows how to handle the following chunks
StreamBegin begin = new StreamBegin();
begin.total = stream.stream.available();
begin.type = Registrator.getID(stream.getClass());
connection.sendTCP(begin);
id = begin.id;
}
@Override
protected Object next(byte[] bytes){
StreamChunk chunk = new StreamChunk();
chunk.id = id;
chunk.data = bytes;
return chunk; //wrap the byte[] with an object so the receiving side knows how to handle it.
}
});
}
@Override
public void send(Object object, SendMode mode){
try{
if(mode == SendMode.tcp){
connection.sendTCP(object);
}else{
connection.sendUDP(object);
}
}catch(Exception e){
Log.err(e);
Log.info("Error sending packet. Disconnecting invalid client!");
connection.close(DcReason.error);
ArcConnection k = getByArcID(connection.getID());
if(k != null) connections.remove(k);
}
}
@Override
public void close(){
if(connection.isConnected()) connection.close(DcReason.closed);
}
}
@SuppressWarnings("unchecked")
public static class PacketSerializer implements NetSerializer{
@Override
public void write(ByteBuffer byteBuffer, Object o){
if(o instanceof FrameworkMessage){
byteBuffer.put((byte)-2); //code for framework message
writeFramework(byteBuffer, (FrameworkMessage)o);
}else{
if(!(o instanceof Packet))
throw new RuntimeException("All sent objects must implement be Packets! Class: " + o.getClass());
byte id = Registrator.getID(o.getClass());
if(id == -1)
throw new RuntimeException("Unregistered class: " + o.getClass());
byteBuffer.put(id);
((Packet)o).write(byteBuffer);
}
}
@Override
public Object read(ByteBuffer byteBuffer){
byte id = byteBuffer.get();
if(id == -2){
return readFramework(byteBuffer);
}else{
Packet packet = Pools.obtain((Class<Packet>)Registrator.getByID(id).type, (Supplier<Packet>)Registrator.getByID(id).constructor);
packet.read(byteBuffer);
return packet;
}
}
public void writeFramework(ByteBuffer buffer, FrameworkMessage message){
if(message instanceof Ping){
Ping p = (Ping)message;
buffer.put((byte)0);
buffer.putInt(p.id);
buffer.put(p.isReply ? 1 : (byte)0);
}else if(message instanceof DiscoverHost){
buffer.put((byte)1);
}else if(message instanceof KeepAlive){
buffer.put((byte)2);
}else if(message instanceof RegisterUDP){
RegisterUDP p = (RegisterUDP)message;
buffer.put((byte)3);
buffer.putInt(p.connectionID);
}else if(message instanceof RegisterTCP){
RegisterTCP p = (RegisterTCP)message;
buffer.put((byte)4);
buffer.putInt(p.connectionID);
}
}
public FrameworkMessage readFramework(ByteBuffer buffer){
byte id = buffer.get();
if(id == 0){
Ping p = new Ping();
p.id = buffer.getInt();
p.isReply = buffer.get() == 1;
return p;
}else if(id == 1){
return new DiscoverHost();
}else if(id == 2){
return new KeepAlive();
}else if(id == 3){
RegisterUDP p = new RegisterUDP();
p.connectionID = buffer.getInt();
return p;
}else if(id == 4){
RegisterTCP p = new RegisterTCP();
p.connectionID = buffer.getInt();
return p;
}else{
throw new RuntimeException("Unknown framework message!");
}
}
}
}

View File

@@ -17,6 +17,7 @@ import java.nio.file.Files;
import java.nio.file.*;
import java.text.*;
import java.util.*;
import static io.anuke.mindustry.Vars.*;
public class CrashSender{
@@ -24,8 +25,8 @@ public class CrashSender{
try{
exception.printStackTrace();
//don't create crash logs for me (anuke) or custom builds, as it's expected
if(System.getProperty("user.name").equals("anuke") || Version.build == -1) return;
//don't create crash logs for custom builds, as it's expected
if(Version.build == -1) return;
//attempt to load version regardless
if(Version.number == 0){
@@ -78,9 +79,9 @@ public class CrashSender{
//attempt to close connections, if applicable
try{
netActive = Net.active();
netServer = Net.server();
Net.dispose();
netActive = net.active();
netServer = net.server();
net.dispose();
}catch(Throwable ignored){
}

View File

@@ -1,15 +1,18 @@
package io.anuke.mindustry.net;
import io.anuke.mindustry.game.*;
public class Host{
public final String name;
public final String address;
public final String mapname;
public final int wave;
public final int players;
public final int players, playerLimit;
public final int version;
public final String versionType;
public final Gamemode mode;
public Host(String name, String address, String mapname, int wave, int players, int version, String versionType){
public Host(String name, String address, String mapname, int wave, int players, int version, String versionType, Gamemode mode, int playerLimit){
this.name = name;
this.address = address;
this.players = players;
@@ -17,5 +20,7 @@ public class Host{
this.wave = wave;
this.version = version;
this.versionType = versionType;
this.playerLimit = playerLimit;
this.mode = mode;
}
}

View File

@@ -1,5 +1,6 @@
package io.anuke.mindustry.net;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
@@ -17,20 +18,26 @@ import static io.anuke.mindustry.Vars.*;
@SuppressWarnings("unchecked")
public class Net{
private static boolean server;
private static boolean active;
private static boolean clientLoaded;
private static Array<Object> packetQueue = new Array<>();
private static ObjectMap<Class<?>, Consumer> clientListeners = new ObjectMap<>();
private static ObjectMap<Class<?>, BiConsumer<Integer, Object>> serverListeners = new ObjectMap<>();
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();
private boolean server;
private boolean active;
private boolean clientLoaded;
private @Nullable StreamBuilder currentStream;
private final Array<Object> packetQueue = new Array<>();
private final ObjectMap<Class<?>, Consumer> clientListeners = new ObjectMap<>();
private final ObjectMap<Class<?>, BiConsumer<NetConnection, Object>> serverListeners = new ObjectMap<>();
private final IntMap<StreamBuilder> streams = new IntMap<>();
private final NetProvider provider;
private final LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor();
private final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor();
public Net(NetProvider provider){
this.provider = provider;
}
/** Display a network error. Call on the graphics thread. */
public static void showError(Throwable e){
public void showError(Throwable e){
if(!headless){
@@ -39,7 +46,9 @@ public class Net{
t = t.getCause();
}
String error = t.getMessage() == null ? "" : t.getMessage().toLowerCase();
String baseError = Strings.getFinalMesage(e);
String error = baseError == null ? "" : baseError.toLowerCase();
String type = t.getClass().toString().toLowerCase();
boolean isError = false;
@@ -56,18 +65,18 @@ public class Net{
}else if(error.equals("alreadyconnected") || error.contains("connection is closed")){
error = Core.bundle.get("error.alreadyconnected");
}else if(!error.isEmpty()){
error = Core.bundle.get("error.any") + "\n" + Strings.parseException(e, true);
error = Core.bundle.get("error.any");
isError = true;
}
if(isError){
ui.showError(Core.bundle.format("connectfail", error));
ui.showException("$error.any", e);
}else{
ui.showText("", Core.bundle.format("connectfail", error));
}
ui.loadfrag.hide();
if(Net.client()){
if(client()){
netClient.disconnectQuietly();
}
}
@@ -78,7 +87,7 @@ public class Net{
/**
* Sets the client loaded status, or whether it will recieve normal packets from the server.
*/
public static void setClientLoaded(boolean loaded){
public void setClientLoaded(boolean loaded){
clientLoaded = loaded;
if(loaded){
@@ -91,13 +100,18 @@ public class Net{
packetQueue.clear();
}
public void setClientConnected(){
active = true;
server = false;
}
/**
* Connect to an address.
*/
public static void connect(String ip, int port, Runnable success){
public void connect(String ip, int port, Runnable success){
try{
if(!active){
clientProvider.connect(ip, port, success);
provider.connectClient(ip, port, success);
active = true;
server = false;
}else{
@@ -111,8 +125,8 @@ public class Net{
/**
* Host a server at an address.
*/
public static void host(int port) throws IOException{
serverProvider.host(port);
public void host(int port) throws IOException{
provider.hostServer(port);
active = true;
server = true;
@@ -122,32 +136,32 @@ public class Net{
/**
* Closes the server.
*/
public static void closeServer(){
public void closeServer(){
for(NetConnection con : getConnections()){
Call.onKick(con.id, KickReason.serverClose);
Call.onKick(con, KickReason.serverClose);
}
serverProvider.close();
provider.closeServer();
server = false;
active = false;
}
public static void reset(){
public void reset(){
closeServer();
netClient.disconnectNoReset();
}
public static void disconnect(){
clientProvider.disconnect();
public void disconnect(){
provider.disconnectClient();
server = false;
active = false;
}
public static byte[] compressSnapshot(byte[] input){
public byte[] compressSnapshot(byte[] input){
return compressor.compress(input);
}
public static byte[] decompressSnapshot(byte[] input, int size){
public byte[] decompressSnapshot(byte[] input, int size){
return decompressor.decompress(input, size);
}
@@ -155,92 +169,64 @@ public class Net{
* Starts discovering servers on a different thread.
* Callback is run on the main libGDX thread.
*/
public static void discoverServers(Consumer<Host> cons, Runnable done){
clientProvider.discover(cons, done);
public void discoverServers(Consumer<Host> cons, Runnable done){
provider.discoverServers(cons, done);
}
/**
* Returns a list of all connections IDs.
*/
public static Iterable<NetConnection> getConnections(){
return (Iterable<NetConnection>)serverProvider.getConnections();
public Iterable<NetConnection> getConnections(){
return (Iterable<NetConnection>)provider.getConnections();
}
/**
* Returns a connection by ID
*/
public static NetConnection getConnection(int id){
return serverProvider.getByID(id);
}
/**
* Send an object to all connected clients, or to the server if this is a client.
*/
public static void send(Object object, SendMode mode){
/** Send an object to all connected clients, or to the server if this is a client.*/
public void send(Object object, SendMode mode){
if(server){
if(serverProvider != null) serverProvider.send(object, mode);
for(NetConnection con : provider.getConnections()){
con.send(object, mode);
}
}else{
if(clientProvider != null) clientProvider.send(object, mode);
provider.sendClient(object, mode);
}
}
/**
* Send an object to a certain client. Server-side only
*/
public static void sendTo(int id, Object object, SendMode mode){
serverProvider.sendTo(id, object, mode);
/** Send an object to everyone EXCEPT a certain client. Server-side only.*/
public void sendExcept(NetConnection except, Object object, SendMode mode){
for(NetConnection con : getConnections()){
if(con != except){
con.send(object, mode);
}
}
}
/**
* Send an object to everyone EXCEPT certain client. Server-side only
*/
public static void sendExcept(int id, Object object, SendMode mode){
serverProvider.sendExcept(id, object, mode);
}
/**
* Send a stream to a specific client. Server-side only.
*/
public static void sendStream(int id, Streamable stream){
serverProvider.sendStream(id, stream);
}
/**
* Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server.
*/
public static void setClientProvider(ClientProvider provider){
Net.clientProvider = provider;
}
/**
* Sets the net serverProvider, e.g. what handles hosting a server.
*/
public static void setServerProvider(ServerProvider provider){
Net.serverProvider = provider;
public @Nullable StreamBuilder getCurrentStream(){
return currentStream;
}
/**
* Registers a client listener for when an object is recieved.
*/
public static <T> void handleClient(Class<T> type, Consumer<T> listener){
public <T> void handleClient(Class<T> type, Consumer<T> listener){
clientListeners.put(type, listener);
}
/**
* Registers a server listener for when an object is recieved.
*/
public static <T> void handleServer(Class<T> type, BiConsumer<Integer, T> listener){
serverListeners.put(type, (BiConsumer<Integer, Object>)listener);
public <T> void handleServer(Class<T> type, BiConsumer<NetConnection, T> listener){
serverListeners.put(type, (BiConsumer<NetConnection, Object>)listener);
}
/**
* Call to handle a packet being recieved for the client.
*/
public static void handleClientReceived(Object object){
public void handleClientReceived(Object object){
if(object instanceof StreamBegin){
StreamBegin b = (StreamBegin)object;
streams.put(b.id, new StreamBuilder(b));
streams.put(b.id, currentStream = new StreamBuilder(b));
}else if(object instanceof StreamChunk){
StreamChunk c = (StreamChunk)object;
StreamBuilder builder = streams.get(c.id);
@@ -251,6 +237,7 @@ public class Net{
if(builder.isDone()){
streams.remove(builder.id);
handleClientReceived(builder.build());
currentStream = null;
}
}else if(clientListeners.get(object.getClass()) != null){
@@ -271,7 +258,7 @@ public class Net{
/**
* Call to handle a packet being recieved for the server.
*/
public static void handleServerReceived(int connection, Object object){
public void handleServerReceived(NetConnection connection, Object object){
if(serverListeners.get(object.getClass()) != null){
if(serverListeners.get(object.getClass()) != null)
@@ -285,50 +272,33 @@ public class Net{
/**
* Pings a host in an new thread. If an error occured, failed() should be called with the exception.
*/
public static void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed){
clientProvider.pingHost(address, port, valid, failed);
}
/**
* Update client ping.
*/
public static void updatePing(){
clientProvider.updatePing();
}
/**
* Get the client ping. Only valid after updatePing().
*/
public static int getPing(){
return server() ? 0 : clientProvider.getPing();
public void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed){
provider.pingHost(address, port, valid, failed);
}
/**
* Whether the net is active, e.g. whether this is a multiplayer game.
*/
public static boolean active(){
public boolean active(){
return active;
}
/**
* Whether this is a server or not.
*/
public static boolean server(){
public boolean server(){
return server && active;
}
/**
* Whether this is a client or not.
*/
public static boolean client(){
public boolean client(){
return !server && active;
}
public static void dispose(){
if(clientProvider != null) clientProvider.dispose();
if(serverProvider != null) serverProvider.close();
clientProvider = null;
serverProvider = null;
public void dispose(){
provider.dispose();
server = false;
active = false;
}
@@ -337,98 +307,40 @@ public class Net{
tcp, udp
}
/** Client implementation. */
public interface ClientProvider{
/** Networking implementation. */
public interface NetProvider{
/** Connect to a server. */
void connect(String ip, int port, Runnable success) throws IOException;
void connectClient(String ip, int port, Runnable success) throws IOException;
/** Send an object to the server. */
void send(Object object, SendMode mode);
/** Update the ping. Should be done every second or so. */
void updatePing();
/** Get ping in milliseconds. Will only be valid after a call to updatePing. */
int getPing();
void sendClient(Object object, SendMode mode);
/** Disconnect from the server. */
void disconnect();
void disconnectClient();
/**
* 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.
* Callback should be run on the main thread.
* @param done is the callback that should run after discovery.
*/
void discover(Consumer<Host> callback, Runnable done);
void discoverServers(Consumer<Host> callback, Runnable done);
/** Ping a host. If an error occured, failed() should be called with the exception. */
void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed);
/** Close all connections. */
void dispose();
}
/** Server implementation. */
public interface ServerProvider{
/** Host a server at specified port. */
void host(int port) throws IOException;
/** Sends a large stream of data to a specific client. */
default void sendStream(int id, Streamable stream){
NetConnection connection = getByID(id);
if(connection == null) return;
try{
int cid;
StreamBegin begin = new StreamBegin();
begin.total = stream.stream.available();
begin.type = Registrator.getID(stream.getClass());
connection.send(begin, SendMode.tcp);
cid = begin.id;
while(stream.stream.available() > 0){
byte[] bytes = new byte[Math.min(512, stream.stream.available())];
stream.stream.read(bytes);
StreamChunk chunk = new StreamChunk();
chunk.id = cid;
chunk.data = bytes;
connection.send(chunk, SendMode.tcp);
}
}catch(IOException e){
throw new RuntimeException(e);
}
}
default void send(Object object, SendMode mode){
for(NetConnection con : getConnections()){
con.send(object, mode);
}
}
default void sendTo(int id, Object object, SendMode mode){
NetConnection conn = getByID(id);
if(conn == null){
Log.err("Failed to find connection with ID {0}.", id);
return;
}
conn.send(object, mode);
}
default void sendExcept(int id, Object object, SendMode mode){
for(NetConnection con : getConnections()){
if(con.id != id){
con.send(object, mode);
}
}
}
/** Close the server connection. */
void close();
void hostServer(int port) throws IOException;
/** Return all connected users. */
Iterable<? extends NetConnection> getConnections();
/** Returns a connection by ID. */
NetConnection getByID(int id);
/** Close the server connection. */
void closeServer();
/** Close all connections. */
default void dispose(){
disconnectClient();
closeServer();
}
}
}

View File

@@ -1,35 +1,77 @@
package io.anuke.mindustry.net;
import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.Packets.*;
import java.io.*;
import static io.anuke.mindustry.Vars.netServer;
public abstract class NetConnection{
private static int lastID;
public final int id;
public final String address;
public boolean modclient;
public boolean mobile;
public boolean mobile, modclient;
public @Nullable Player player;
/** ID of last recieved client snapshot. */
public int lastRecievedClientSnapshot = -1;
/** Timestamp of last recieved snapshot. */
public long lastRecievedClientTime;
public boolean hasConnected = false;
public boolean hasBegunConnecting = false;
public boolean hasConnected, hasBegunConnecting;
public float viewWidth, viewHeight, viewX, viewY;
/** Assigns this connection a unique ID. No two connections will ever have the same ID.*/
public NetConnection(String address){
this.id = lastID++;
this.address = address;
}
public void kick(KickReason reason){
Log.info("Kicking connection {0}; Reason: {1}", address, reason.name());
if(player != null && (reason == KickReason.kick || reason == KickReason.banned || reason == KickReason.vote) && player.uuid != null){
PlayerInfo info = netServer.admins.getInfo(player.uuid);
info.timesKicked++;
info.lastKicked = Math.max(Time.millis(), info.lastKicked);
}
Call.onKick(this, reason);
Time.runTask(2f, this::close);
netServer.admins.save();
}
public boolean isConnected(){
return true;
}
public void sendStream(Streamable stream){
try{
int cid;
StreamBegin begin = new StreamBegin();
begin.total = stream.stream.available();
begin.type = Registrator.getID(stream.getClass());
send(begin, SendMode.tcp);
cid = begin.id;
while(stream.stream.available() > 0){
byte[] bytes = new byte[Math.min(512, stream.stream.available())];
stream.stream.read(bytes);
StreamChunk chunk = new StreamChunk();
chunk.id = cid;
chunk.data = bytes;
send(chunk, SendMode.tcp);
}
}catch(IOException e){
throw new RuntimeException(e);
}
}
public abstract void send(Object object, SendMode mode);
public abstract void close();

View File

@@ -1,17 +1,15 @@
package io.anuke.mindustry.net;
import io.anuke.arc.Core;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.arc.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.JsonIO;
import io.anuke.mindustry.io.SaveIO;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.Map;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.*;
import java.util.*;
import static io.anuke.mindustry.Vars.*;
@@ -71,8 +69,9 @@ public class NetworkIO{
buffer.putInt(state.wave);
buffer.putInt(Version.build);
writeString(buffer, Version.type);
//TODO additional information:
// - gamemode ID/name (just pick the closest one?)
buffer.put((byte)Gamemode.bestFit(state.rules).ordinal());
buffer.putInt(netServer.admins.getPlayerLimit());
return buffer;
}
@@ -83,13 +82,15 @@ public class NetworkIO{
int wave = buffer.getInt();
int version = buffer.getInt();
String vertype = readString(buffer);
Gamemode gamemode = Gamemode.all[buffer.get()];
int limit = buffer.getInt();
return new Host(host, hostAddress, map, wave, players, version, vertype);
return new Host(host, hostAddress, map, wave, players, version, vertype, gamemode, limit);
}
private static void writeString(ByteBuffer buffer, String string, int maxlen){
byte[] bytes = string.getBytes(charset);
//truncating this way may lead to wierd encoding errors at the ends of strings...
//todo truncating this way may lead to wierd encoding errors at the ends of strings...
if(bytes.length > maxlen){
bytes = Arrays.copyOfRange(bytes, 0, maxlen);
}

View File

@@ -14,7 +14,7 @@ public class Packets{
public enum KickReason{
kick, clientOutdated, serverOutdated, banned, gameover(true), recentKick,
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch, whitelist;
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch, whitelist, playerLimit;
public final boolean quiet;
@@ -41,7 +41,6 @@ public class Packets{
}
public static class Connect implements Packet{
public int id;
public String addressTCP;
@Override
@@ -51,7 +50,6 @@ public class Packets{
}
public static class Disconnect implements Packet{
public int id;
public String reason;
@Override

View File

@@ -16,13 +16,16 @@ public class Streamable implements Packet{
public final int id;
public final byte type;
public final int total;
public final ByteArrayOutputStream stream;
public final ByteArrayOutputStream stream = new ByteArrayOutputStream();
public StreamBuilder(StreamBegin begin){
id = begin.id;
type = begin.type;
total = begin.total;
stream = new ByteArrayOutputStream();
}
public float progress(){
return (float)stream.size() / total;
}
public void add(byte[] bytes){

View File

@@ -1,9 +1,16 @@
package io.anuke.mindustry.plugin;
import io.anuke.arc.files.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
public abstract class Plugin{
/** @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(){

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