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

# Conflicts:
#	core/assets/sprites/sprites.atlas
#	core/assets/sprites/sprites.png
#	core/assets/version.properties
#	core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java
This commit is contained in:
Anuken
2018-02-27 19:58:34 -05:00
59 changed files with 2419 additions and 1120 deletions

View File

@@ -93,7 +93,7 @@ public class Vars{
public static final int tilesize = 8;
public static final Locale[] locales = {new Locale("en"), new Locale("fr", "FR"), new Locale("ru"), new Locale("pl", "PL"),
new Locale("es", "LA"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID")};
new Locale("de"), new Locale("es", "LA"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID")};
public static final Color[] playerColors = {
Color.valueOf("82759a"),

View File

@@ -169,8 +169,8 @@ public class Pathfind{
/**Reset and clear the paths.*/
public void resetPaths(){
for(SpawnPoint point : world.getSpawns()){
resetPathFor(point);
for(int i = 0; i < world.getSpawns().size; i ++){
resetPathFor(world.getSpawns().get(i));
}
}

View File

@@ -121,7 +121,7 @@ public class Logic extends Module {
if(world.getCore() != null && world.getCore().block() != ProductionBlocks.core && !state.gameOver){
state.gameOver = true;
NetEvents.handleGameOver();
if(Net.server()) NetEvents.handleGameOver();
Events.fire(GameOverEvent.class);
}

View File

@@ -17,7 +17,9 @@ import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.Item;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Map;
import io.anuke.mindustry.world.Placement;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.ProductionBlocks;
import io.anuke.ucore.core.Timers;
@@ -49,6 +51,7 @@ public class NetClient extends Module {
public NetClient(){
Net.handleClient(Connect.class, packet -> {
player.isAdmin = false;
Net.setClientLoaded(false);
recieved.clear();
@@ -159,12 +162,22 @@ public class NetClient extends Module {
state.wavetime = packet.countdown;
state.wave = packet.wave;
//removed: messing with time isn't necessary anymore
//Timers.resetTime(packet.time + (float) (TimeUtils.timeSinceMillis(packet.timestamp) / 1000.0 * 60.0));
ui.hudfrag.updateItems();
});
Net.handleClient(PlacePacket.class, (packet) -> {
Placement.placeBlock(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, false);
if(packet.playerid == player.id){
Tile tile = world.tile(packet.x, packet.y);
if(tile != null) Block.getByID(packet.block).placed(tile);
}
});
Net.handleClient(BreakPacket.class, (packet) -> {
Placement.breakBlock(packet.x, packet.y, true, false);
});
Net.handleClient(EntitySpawnPacket.class, packet -> {
EntityGroup group = packet.group;
@@ -316,6 +329,22 @@ public class NetClient extends Module {
r.run();
}
});
Net.handleClient(NetErrorPacket.class, packet -> {
ui.showError(packet.message);
disconnectQuietly();
});
Net.handleClient(PlayerAdminPacket.class, packet -> {
Player player = playerGroup.getByID(packet.id);
player.isAdmin = packet.admin;
ui.listfrag.rebuild();
});
Net.handleClient(TracePacket.class, packet -> {
Player player = playerGroup.getByID(packet.info.playerid);
ui.traces.show(player, packet.info);
});
}
@Override
@@ -329,12 +358,6 @@ public class NetClient extends Module {
}
}
//TODO remove.
public void test(){
gotData = false;
connecting = true;
}
public boolean hasData(){
return gotData;
}

View File

@@ -4,11 +4,8 @@ import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
import io.anuke.mindustry.resource.Upgrade;
import io.anuke.mindustry.resource.Weapon;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.modules.Module;
@@ -25,23 +22,6 @@ public class NetCommon extends Module {
weapon.shoot(player, packet.x, packet.y, packet.rotation);
});
Net.handle(PlacePacket.class, (packet) -> {
if(headless)
world.placeBlock(packet.x, packet.y, Block.getByID(packet.block), packet.rotation);
else
control.input().placeBlockInternal(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, false);
Recipe recipe = Recipes.getByResult(Block.getByID(packet.block));
if (recipe != null) state.inventory.removeItems(recipe.requirements);
});
Net.handle(BreakPacket.class, (packet) -> {
if(headless)
world.removeBlock(world.tile(packet.x, packet.y));
else
control.input().breakBlockInternal(packet.x, packet.y, false);
});
Net.handle(ChatPacket.class, (packet) -> {
ui.chatfrag.addMessage(packet.text, colorizeName(packet.id, packet.name));
});

View File

@@ -7,14 +7,15 @@ import io.anuke.mindustry.entities.SyncEntity;
import io.anuke.mindustry.game.EventType.GameOverEvent;
import io.anuke.mindustry.io.Platform;
import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Administration;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.Upgrade;
import io.anuke.mindustry.resource.UpgradeRecipes;
import io.anuke.mindustry.resource.Weapon;
import io.anuke.mindustry.resource.*;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Placement;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.core.Events;
import io.anuke.ucore.core.Timers;
@@ -41,6 +42,8 @@ public class NetServer extends Module{
private final static int timerStateSync = 1;
private final static int timerBlockSync = 2;
public final Administration admins = new Administration();
/**Maps connection IDs to players.*/
private IntMap<Player> connections = new IntMap<>();
private ObjectMap<String, ByteArray> weapons = new ObjectMap<>();
@@ -51,18 +54,33 @@ public class NetServer extends Module{
Events.on(GameOverEvent.class, () -> weapons.clear());
Net.handleServer(Connect.class, (id, connect) -> {});
Net.handleServer(Connect.class, (id, connect) -> {
if(admins.isBanned(connect.addressTCP)){
Net.kickConnection(id, KickReason.banned);
}
});
Net.handleServer(ConnectPacket.class, (id, packet) -> {
if(Net.getConnection(id) == null ||
admins.isBanned(Net.getConnection(id).address)) return;
if(packet.version != Version.build && packet.version != -1 && Version.build != -1){ //ignore 'custom builds' on both ends
String ip = Net.getConnection(id).address;
admins.setKnownName(ip, packet.name);
if(packet.version != Version.build && Version.build != -1 && packet.version != -1){
Net.kickConnection(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
return;
}
if(packet.version == -1){
admins.getTrace(ip).modclient = true;
}
Log.info("Sending data to player '{0}' / {1}", packet.name, id);
Player player = new Player();
player.isAdmin = admins.isAdmin(Net.getConnection(id).address);
player.clientid = id;
player.name = packet.name;
player.isAndroid = packet.android;
@@ -72,6 +90,8 @@ public class NetServer extends Module{
player.color.set(packet.color);
connections.put(id, player);
admins.getTrace(ip).playerid = player.id;
if(world.getMap().custom){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
NetworkIO.writeMap(world.getMap(), stream);
@@ -92,7 +112,7 @@ public class NetServer extends Module{
Player player = connections.get(id);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
NetworkIO.writeWorld(player.id, weapons.get(player.name, new ByteArray()), stream);
NetworkIO.writeWorld(player, weapons.get(player.name, new ByteArray()), stream);
WorldData data = new WorldData();
data.stream = new ByteArrayInputStream(stream.toByteArray());
Net.sendStream(id, data);
@@ -145,19 +165,53 @@ public class NetServer extends Module{
Net.handleServer(PlacePacket.class, (id, packet) -> {
packet.playerid = connections.get(id).id;
Net.sendExcept(id, packet, SendMode.tcp);
Block block = Block.getByID(packet.block);
if(!Placement.validPlace(packet.x, packet.y, block)) return;
Recipe recipe = Recipes.getByResult(block);
if(recipe == null) return;
state.inventory.removeItems(recipe.requirements);
Placement.placeBlock(packet.x, packet.y, block, packet.rotation, true, false);
admins.getTrace(Net.getConnection(id).address).lastBlockPlaced = block;
admins.getTrace(Net.getConnection(id).address).totalBlocksPlaced ++;
Net.send(packet, SendMode.tcp);
});
Net.handleServer(BreakPacket.class, (id, packet) -> {
packet.playerid = connections.get(id).id;
Net.sendExcept(id, packet, SendMode.tcp);
if(!Placement.validBreak(packet.x, packet.y)) return;
Block block = Placement.breakBlock(packet.x, packet.y, true, false);
if(block != null) {
admins.getTrace(Net.getConnection(id).address).lastBlockBroken = block;
admins.getTrace(Net.getConnection(id).address).totalBlocksBroken++;
if (block.update || block.destructible)
admins.getTrace(Net.getConnection(id).address).structureBlocksBroken++;
}
Net.send(packet, SendMode.tcp);
});
Net.handleServer(ChatPacket.class, (id, packet) -> {
if(!Timers.get("chatFlood" + id, 20)){
ChatPacket warn = new ChatPacket();
warn.text = "[scarlet]You are sending messages too quickly.";
Net.sendTo(id, warn, SendMode.tcp);
return;
}
Player player = connections.get(id);
packet.name = player.name;
packet.id = player.id;
Net.sendExcept(player.clientid, packet, SendMode.tcp);
Net.send(packet, SendMode.tcp);
});
Net.handleServer(UpgradePacket.class, (id, packet) -> {
@@ -200,6 +254,39 @@ public class NetServer extends Module{
packet.id = connections.get(id).id;
Net.sendExcept(id, packet, SendMode.tcp);
});
Net.handleServer(AdministerRequestPacket.class, (id, packet) -> {
Player player = connections.get(id);
if(!player.isAdmin){
Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.",
player.name, Net.getConnection(player.clientid).address);
return;
}
Player other = playerGroup.getByID(packet.id);
if(other == null || other.isAdmin){
Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name);
return;
}
String ip = Net.getConnection(other.clientid).address;
if(packet.action == AdminAction.ban){
admins.banPlayer(ip);
Net.kickConnection(other.clientid, KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name, other.name);
}else if(packet.action == AdminAction.kick){
Net.kickConnection(other.clientid, KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
}else if(packet.action == AdminAction.trace){
TracePacket trace = new TracePacket();
trace.info = admins.getTrace(ip);
Net.sendTo(id, trace, SendMode.tcp);
Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name);
}
});
}
public void update(){
@@ -221,6 +308,7 @@ public class NetServer extends Module{
public void reset(){
weapons.clear();
admins.clearTraces();
}
void sync(){

View File

@@ -264,8 +264,14 @@ public class Renderer extends RendererModule{
Draw.color();
Draw.tcolor(player.getColor());
Draw.text(player.name, player.x, player.y + 8);
Draw.tcolor();
}
if(player.isAdmin){
Draw.color(player.getColor());
float s = 3f;
Draw.rect("icon-admin-small", player.x + layout.width/2f + 2 + 1, player.y + 7f, s, s);
}
Draw.reset();
}
}
Pools.free(layout);
Draw.tscl(fontscale);
@@ -274,6 +280,7 @@ public class Renderer extends RendererModule{
void drawEnemyMarkers(){
Graphics.surface(indicatorSurface);
Draw.color(Color.RED);
for(Enemy enemy : enemyGroup.all()) {
if (rect.setSize(camera.viewportWidth, camera.viewportHeight).setCenter(camera.position.x, camera.position.y)
@@ -409,8 +416,10 @@ public class Renderer extends RendererModule{
Lines.dashCircle(spawn.start.worldx(), spawn.start.worldy(), enemyspawnspace);
}
Draw.color(Color.LIME);
Lines.poly(world.getSpawnX(), world.getSpawnY(), 4, 6f, Timers.time()*2f);
if(world.getCore() != null) {
Draw.color(Color.LIME);
Lines.poly(world.getSpawnX(), world.getSpawnY(), 4, 6f, Timers.time() * 2f);
}
if(input.breakMode == PlaceMode.holdDelete)
input.breakMode.draw(tilex, tiley, 0, 0);

View File

@@ -27,7 +27,10 @@ public class ThreadHandler {
public ThreadHandler(ThreadProvider impl){
this.impl = impl;
Timers.setDeltaProvider(() -> impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime()*60f);
Timers.setDeltaProvider(() ->{
float result = impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime()*60f;
return Float.isNaN(result) ? 1f : result;
});
}
public void run(Runnable r){

View File

@@ -45,6 +45,9 @@ public class UI extends SceneModule{
public ControlsDialog controls;
public MapEditorDialog editor;
public LanguageDialog language;
public BansDialog bans;
public AdminsDialog admins;
public TraceDialog traces;
public final MenuFragment menufrag = new MenuFragment();
public final ToolFragment toolfrag = new ToolFragment();
@@ -150,6 +153,9 @@ public class UI extends SceneModule{
paused = new PausedDialog();
about = new AboutDialog();
host = new HostDialog();
bans = new BansDialog();
admins = new AdminsDialog();
traces = new TraceDialog();
build.begin(scene);

View File

@@ -8,10 +8,7 @@ import io.anuke.mindustry.ai.Pathfind;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.game.SpawnPoint;
import io.anuke.mindustry.io.Maps;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Map;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.WorldGenerator;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.DistributionBlocks;
import io.anuke.mindustry.world.blocks.ProductionBlocks;
@@ -22,7 +19,8 @@ import io.anuke.ucore.modules.Module;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Tmp;
import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.Vars.control;
import static io.anuke.mindustry.Vars.tilesize;
public class World extends Module{
private int seed;
@@ -182,7 +180,7 @@ public class World extends Module{
core = WorldGenerator.generate(map.pixmap, tiles, spawns);
placeBlock(core.x, core.y, ProductionBlocks.core, 0);
Placement.placeBlock(core.x, core.y, ProductionBlocks.core, 0, false, false);
if(!map.name.equals("tutorial")){
setDefaultBlocks();
@@ -233,7 +231,7 @@ public class World extends Module{
public int getSeed(){
return seed;
}
public void removeBlock(Tile tile){
if(!tile.block().isMultiblock() && !tile.isLinked()){
tile.setBlock(Blocks.air);
@@ -246,32 +244,6 @@ public class World extends Module{
}
}
}
public void placeBlock(int x, int y, Block result, int rotation){
Tile tile = world.tile(x, y);
//just in case
if(tile == null) return;
tile.setBlock(result, rotation);
if(result.isMultiblock()){
int offsetx = -(result.width-1)/2;
int offsety = -(result.height-1)/2;
for(int dx = 0; dx < result.width; dx ++){
for(int dy = 0; dy < result.height; dy ++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(!(worldx == x && worldy == y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null)
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
}
}
}
}
}
public TileEntity findTileTarget(float x, float y, Tile tile, float range, boolean damaged){
Entity closest = null;

View File

@@ -34,6 +34,7 @@ public class Player extends SyncEntity{
public String name = "name";
public boolean isAndroid;
public boolean isAdmin;
public Color color = new Color();
public Weapon weaponLeft = Weapon.blaster;
@@ -44,7 +45,7 @@ public class Player extends SyncEntity{
public float stucktime = 0f;
public boolean dashing = false;
public int clientid;
public int clientid = -1;
public boolean isLocal = false;
public Timer timer = new Timer(4);
@@ -83,7 +84,7 @@ public class Player extends SyncEntity{
@Override
public void onDeath(){
remove();
dead = true;
if(Net.active()){
NetEvents.handlePlayerDeath();
}
@@ -112,7 +113,7 @@ public class Player extends SyncEntity{
@Override
public void drawSmooth(){
if((debug && (!showPlayer || !showUI)) || (isAndroid && isLocal) || (dead && !isLocal)) return;
if((debug && (!showPlayer || !showUI)) || (isAndroid && isLocal) || dead) return;
boolean snap = snapCamera && Settings.getBool("smoothcam") && Settings.getBool("pixelate") && isLocal;
String part = isAndroid ? "ship" : "mech";
@@ -155,6 +156,8 @@ public class Player extends SyncEntity{
return;
}
if(isDead()) return;
Tile tile = world.tileWorld(x, y);
//if player is in solid block
@@ -240,6 +243,7 @@ public class Player extends SyncEntity{
buffer.put(weaponLeft.id);
buffer.put(weaponRight.id);
buffer.put(isAndroid ? 1 : (byte)0);
buffer.put(isAdmin ? 1 : (byte)0);
buffer.putInt(Color.rgba8888(color));
buffer.putFloat(x);
buffer.putFloat(y);
@@ -254,6 +258,7 @@ public class Player extends SyncEntity{
weaponLeft = (Weapon) Upgrade.getByID(buffer.get());
weaponRight = (Weapon) Upgrade.getByID(buffer.get());
isAndroid = buffer.get() == 1;
isAdmin = buffer.get() == 1;
color.set(buffer.getInt());
x = buffer.getFloat();
y = buffer.getFloat();

View File

@@ -17,6 +17,7 @@ import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.graphics.Lines;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Strings;
@@ -82,6 +83,13 @@ public class EnemyType {
Graphics.flush();
if(isCalculating(enemy)){
Draw.color(Color.SKY);
Lines.polySeg(20, 0, 4, enemy.x, enemy.y, 11f, Timers.time() * 2f + enemy.id*52f);
Lines.polySeg(20, 0, 4, enemy.x, enemy.y, 11f, Timers.time() * 2f + enemy.id*52f + 180f);
Draw.color();
}
if(showPaths){
Draw.tscl(0.25f);
Draw.text((int)enemy.idletime + " " + enemy.node + " " + enemy.id + "\n" + Strings.toFixed(enemy.totalMove.x, 2) + ", "
@@ -100,9 +108,10 @@ public class EnemyType {
enemy.hitTime -= Timers.delta();
}
if(enemy.lane >= world.getSpawns().size) enemy.lane = 0;
if(enemy.lane >= world.getSpawns().size || enemy.lane < 0) enemy.lane = 0;
boolean waiting = world.getSpawns().get(enemy.lane).pathTiles == null || enemy.node <= 0;
boolean waiting = enemy.lane >= world.getSpawns().size || enemy.lane < 0
|| world.getSpawns().get(enemy.lane).pathTiles == null || enemy.node <= 0;
move(enemy);
@@ -156,6 +165,8 @@ public class EnemyType {
Tile core = world.getCore();
if(core == null) return;
if(enemy.idletime > maxIdleLife && enemy.node > 0){
enemy.onDeath();
return;
@@ -189,7 +200,7 @@ public class EnemyType {
}else if(dst < avoidRange){
calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed);
shift.add(calc.scl(1.1f));
}else if(dst < attractRange && !nearCore){
}else if(dst < attractRange && !nearCore && !isCalculating(enemy)){
calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed);
shift.add(calc.scl(-1));
}
@@ -217,7 +228,8 @@ public class EnemyType {
//no tile found
if(enemy.target == null){
enemy.target = Entities.getClosest(playerGroup, enemy.x, enemy.y, range, e -> !((Player)e).isAndroid);
enemy.target = Entities.getClosest(playerGroup, enemy.x, enemy.y, range, e -> !((Player)e).isAndroid &&
!((Player)e).isDead());
}
}else if(nearCore){
enemy.target = world.getCore().entity;
@@ -267,6 +279,10 @@ public class EnemyType {
}
}
public boolean isCalculating(Enemy enemy){
return enemy.node < 0 && !Net.client();
}
public static EnemyType getByID(byte id){
return types.get(id);
}

View File

@@ -56,4 +56,9 @@ public class TargetType extends EnemyType {
new Enemy(EnemyTypes.target).set(enemy.x, enemy.y).add();
});
}
@Override
public boolean isCalculating(Enemy enemy){
return false;
}
}

View File

@@ -177,7 +177,7 @@ public class BlockRenderer{
OrthographicCamera camera = Core.camera;
Graphics.end();
if(Graphics.drawing()) Graphics.end();
int crangex = (int)(camera.viewportWidth * camera.zoom / (chunksize * tilesize))+1;
int crangey = (int)(camera.viewportHeight * camera.zoom / (chunksize * tilesize))+1;

View File

@@ -2,26 +2,16 @@ package io.anuke.mindustry.input;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.game.SpawnPoint;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetEvents;
import io.anuke.mindustry.resource.ItemStack;
import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Placement;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.ProductionBlocks;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Sounds;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.SolidEntity;
import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.*;
@@ -35,8 +25,6 @@ public abstract class InputHandler extends InputAdapter{
public PlaceMode lastPlaceMode = placeMode;
public PlaceMode lastBreakMode = breakMode;
private Rectangle rect = new Rectangle();
public abstract void update();
public abstract float getCursorX();
public abstract float getCursorY();
@@ -88,39 +76,6 @@ public abstract class InputHandler extends InputAdapter{
public boolean validPlace(int x, int y, Block type){
for(SpawnPoint spawn : world.getSpawns()){
if(Vector2.dst(x * tilesize, y * tilesize, spawn.start.worldx(), spawn.start.worldy()) < enemyspawnspace){
return false;
}
}
rect.setSize(type.width * tilesize, type.height * tilesize);
Vector2 offset = type.getPlaceOffset();
rect.setCenter(offset.x + x * tilesize, offset.y + y * tilesize);
synchronized (Entities.entityLock) {
for (SolidEntity e : Entities.getNearby(enemyGroup, x * tilesize, y * tilesize, tilesize * 2f)) {
if (e == null) continue; //not sure why this happens?
Rectangle rect = e.hitbox.getRect(e.x, e.y);
if (this.rect.overlaps(rect)) {
return false;
}
}
}
if(type.solid || type.solidifes) {
for (Player player : playerGroup.all()) {
if (!player.isAndroid && rect.overlaps(player.hitbox.getRect(player.x, player.y))) {
return false;
}
}
}
Tile tile = world.tile(x, y);
if(tile == null || (isSpawnPoint(tile) && (type.solidifes || type.solid))) return false;
if(!type.isMultiblock() && control.tutorial().active() &&
control.tutorial().showBlock()){
@@ -135,40 +90,11 @@ public abstract class InputHandler extends InputAdapter{
}else if(control.tutorial().active()){
return false;
}
if(type.isMultiblock()){
int offsetx = -(type.width-1)/2;
int offsety = -(type.height-1)/2;
for(int dx = 0; dx < type.width; dx ++){
for(int dy = 0; dy < type.height; dy ++){
Tile other = world.tile(x + dx + offsetx, y + dy + offsety);
if(other == null || (other.block() != Blocks.air && !other.block().alwaysReplace) || isSpawnPoint(other)){
return false;
}
}
}
return true;
}else{
if(tile.block() != type && (type.canReplace(tile.block()) || tile.block().alwaysReplace) && tile.block().isMultiblock() == type.isMultiblock()){
return true;
}
return tile.block() == Blocks.air;
}
}
public boolean isSpawnPoint(Tile tile){
return tile != null && tile.x == world.getCore().x && tile.y == world.getCore().y - 2;
return Placement.validPlace(x, y, type);
}
public boolean validBreak(int x, int y){
Tile tile = world.tile(x, y);
if(tile == null || tile.block() == ProductionBlocks.core) return false;
if(tile.isLinked() && tile.getLinked().block() == ProductionBlocks.core){
return false;
}
if(control.tutorial().active()){
if(control.tutorial().showBlock()){
@@ -185,102 +111,26 @@ public abstract class InputHandler extends InputAdapter{
}
}
return tile.breakable();
return Placement.validBreak(x, y);
}
public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
if(!Net.client()){
Placement.placeBlock(x, y, result, rotation, effects, sound);
Tile tile = world.tile(x, y);
if(tile != null) result.placed(tile);
}
placeBlockInternal(x, y, result, rotation, effects, sound);
Tile tile = world.tile(x, y);
if(tile != null) result.placed(tile);
if(Net.active() && result != ProductionBlocks.core){
if(Net.active()){
NetEvents.handlePlace(x, y, result, rotation);
}
}
public void placeBlockInternal(int x, int y, Block result, int rotation, boolean effects, boolean sound){
Tile tile = world.tile(x, y);
//just in case
if(tile == null)
return;
tile.setBlock(result, rotation);
if(result.isMultiblock()){
int offsetx = -(result.width-1)/2;
int offsety = -(result.height-1)/2;
for(int dx = 0; dx < result.width; dx ++){
for(int dy = 0; dy < result.height; dy ++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(!(worldx == x && worldy == y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null)
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
}
if(effects) Effects.effect(Fx.place, worldx * tilesize, worldy * tilesize);
}
}
}else{
if(effects) Effects.effect(Fx.place, x * tilesize, y * tilesize);
}
if(effects && sound) Sounds.play("place");
}
public void breakBlock(int x, int y, boolean sound){
breakBlockInternal(x, y, sound);
if(!Net.client()) Placement.breakBlock(x, y, true, sound);
if(Net.active()){
NetEvents.handleBreak(x, y);
}
}
public void breakBlockInternal(int x, int y, boolean sound){
Tile tile = world.tile(x, y);
if(tile == null) return;
Block block = tile.isLinked() ? tile.getLinked().block() : tile.block();
Recipe result = null;
for(Recipe recipe : Recipes.all()){
if(recipe.result == block){
result = recipe;
break;
}
}
if(result != null){
for(ItemStack stack : result.requirements){
state.inventory.addItem(stack.item, (int)(stack.amount * breakDropAmount));
}
}
if(tile.block().drops != null){
state.inventory.addItem(tile.block().drops.item, tile.block().drops.amount);
}
//Effects.shake(3f, 1f, player);
if(sound) Sounds.play("break");
if(!tile.block().isMultiblock() && !tile.isLinked()){
tile.setBlock(Blocks.air);
Effects.effect(Fx.breakBlock, tile.worldx(), tile.worldy());
}else{
Tile target = tile.isLinked() ? tile.getLinked() : tile;
Array<Tile> removals = target.getLinkedTiles();
for(Tile toremove : removals){
//note that setting a new block automatically unlinks it
toremove.setBlock(Blocks.air);
Effects.effect(Fx.breakBlock, toremove.worldx(), toremove.worldy());
}
}
}
}

View File

@@ -75,13 +75,14 @@ public class Maps implements Disposable{
}
public void loadMaps(){
if(!loadMapFile(Gdx.files.internal("maps/maps.json"))){
if(!loadMapFile(Gdx.files.internal("maps/maps.json"), true)){
throw new RuntimeException("Failed to load maps!");
}
if(!gwt) {
if (!loadMapFile(customMapDirectory.child("maps.json"))) {
if (!loadMapFile(customMapDirectory.child("maps.json"), false)) {
try {
Log.info("Failed to find custom map directory.");
customMapDirectory.child("maps.json").writeString("{}", false);
} catch (Exception e) {
throw new RuntimeException("Failed to create custom map directory!");
@@ -159,25 +160,30 @@ public class Maps implements Disposable{
saveMaps(out, customMapDirectory.child("maps.json"));
}
private boolean loadMapFile(FileHandle file){
try{
private boolean loadMapFile(FileHandle file, boolean logException){
try {
Array<Map> arr = json.fromJson(ArrayContainer.class, file).maps;
if(arr != null){ //can be an empty map file
for(Map map : arr){
if (arr != null) { //can be an empty map file
for (Map map : arr) {
map.pixmap = new Pixmap(file.sibling(map.name + ".png"));
if(!headless) map.texture = new Texture(map.pixmap);
if (!headless) map.texture = new Texture(map.pixmap);
maps.put(map.id, map);
mapNames.put(map.name, map);
lastID = Math.max(lastID, map.id);
if(!map.custom){
if (!map.custom) {
defaultMaps.add(map);
}
}
}
return true;
}catch(Exception e){
}catch (GdxRuntimeException e){
Log.err(e);
Log.err("Failed loading map file: {0}", file);
return true;
}catch(Exception e){
if(logException) {
Log.err(e);
Log.err("Failed loading map file: {0}", file);
}
return false;
}
}

View File

@@ -307,7 +307,7 @@ public class Save15 extends SaveFileVersion {
for(int y = 0; y < world.height(); y ++){
Tile tile = world.tile(x, y);
if(tile.breakable()){
if(tile != null && tile.breakable()){
if(tile.block() instanceof Rock){
totalrocks ++;
}else{
@@ -325,7 +325,7 @@ public class Save15 extends SaveFileVersion {
for (int y = 0; y < world.height(); y++) {
Tile tile = world.tile(x, y);
if (tile.block() instanceof Rock) {
if (tile != null && tile.block() instanceof Rock) {
stream.writeInt(tile.packedPosition());
}
}
@@ -338,7 +338,7 @@ public class Save15 extends SaveFileVersion {
for(int y = 0; y < world.height(); y ++){
Tile tile = world.tile(x, y);
if(tile.breakable() && !(tile.block() instanceof Rock)){
if(tile != null && tile.breakable() && !(tile.block() instanceof Rock)){
stream.writeInt(x + y*world.width()); //tile pos
stream.writeInt(tile.block().id); //block ID

View File

@@ -0,0 +1,125 @@
package io.anuke.mindustry.net;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.ObjectMap;
import io.anuke.ucore.core.Settings;
public class Administration {
private Json json = new Json();
private Array<String> bannedIPS = new Array<>();
private Array<String> admins = new Array<>();
private ObjectMap<String, String> known = new ObjectMap<>();
private ObjectMap<String, TraceInfo> traces = new ObjectMap<>();
public Administration(){
Settings.defaultList(
"bans", "{}",
"admins", "{}",
"knownIPs", "{}"
);
load();
}
public TraceInfo getTrace(String ip){
if(!traces.containsKey(ip)) traces.put(ip, new TraceInfo(ip));
return traces.get(ip);
}
public void clearTraces(){
traces.clear();
}
/**Sets last known name for an IP.*/
public void setKnownName(String ip, String name){
known.put(ip, name);
saveKnown();
}
/**Returns the last known name for an IP. Returns 'unknown' if this IP has an unknown username.*/
public String getLastName(String ip){
return known.get(ip, "unknown");
}
/**Returns list of banned IPs.*/
public Array<String> getBanned(){
return bannedIPS;
}
/**Bans a player by IP; returns whether this player was already banned.*/
public boolean banPlayer(String ip){
if(bannedIPS.contains(ip, false))
return false;
bannedIPS.add(ip);
saveBans();
return true;
}
/**Unbans a player by IP; returns whether this player was banned in the first place..*/
public boolean unbanPlayer(String ip){
if(!bannedIPS.contains(ip, false))
return false;
bannedIPS.removeValue(ip, false);
saveBans();
return true;
}
/**Returns list of banned IPs.*/
public Array<String> getAdmins(){
return admins;
}
/**Makes a player an admin. Returns whether this player was already an admin.*/
public boolean adminPlayer(String ip){
if(admins.contains(ip, false))
return false;
admins.add(ip);
saveAdmins();
return true;
}
/**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/
public boolean unAdminPlayer(String ip){
if(!admins.contains(ip, false))
return false;
admins.removeValue(ip, false);
saveAdmins();
return true;
}
public boolean isBanned(String ip){
return bannedIPS.contains(ip, false);
}
public boolean isAdmin(String ip){
return admins.contains(ip, false);
}
private void saveKnown(){
Settings.putString("knownIPs", json.toJson(known));
Settings.save();
}
private void saveBans(){
Settings.putString("bans", json.toJson(bannedIPS));
Settings.save();
}
private void saveAdmins(){
Settings.putString("admins", json.toJson(admins));
Settings.save();
}
private void load(){
bannedIPS = json.fromJson(Array.class, Settings.getString("bans"));
admins = json.fromJson(Array.class, Settings.getString("admins"));
known = json.fromJson(ObjectMap.class, Settings.getString("knownIPs"));
}
}

View File

@@ -3,11 +3,15 @@ package io.anuke.mindustry.net;
public class Host {
public final String name;
public final String address;
public final String mapname;
public final int wave;
public final int players;
public Host(String name, String address, int players){
public Host(String name, String address, String mapname, int wave, int players){
this.name = name;
this.address = address;
this.players = players;
this.mapname = mapname;
this.wave = wave;
}
}

View File

@@ -210,7 +210,7 @@ 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<IOException> failed){
public static void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed){
clientProvider.pingHost(address, port, valid, failed);
}
@@ -285,7 +285,7 @@ public class Net{
* Callback should be run on libGDX main thread.*/
void discover(Consumer<Array<Host>> callback);
/**Ping a host. If an error occured, failed() should be called with the exception. */
void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> failed);
void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed);
/**Close all connections.*/
void dispose();
}

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry.net;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.net.Net.SendMode;
@@ -12,8 +13,7 @@ import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.entities.Entity;
import static io.anuke.mindustry.Vars.netCommon;
import static io.anuke.mindustry.Vars.ui;
import static io.anuke.mindustry.Vars.*;
public class NetEvents {
@@ -99,8 +99,6 @@ public class NetEvents {
packet.name = Vars.player.name;
packet.id = Vars.player.id;
Net.send(packet, SendMode.tcp);
ui.chatfrag.addMessage(packet.text, netCommon.colorizeName(Vars.player.id, Vars.player.name));
}
public static void handleShoot(Weapon weapon, float x, float y, float angle){
@@ -152,4 +150,27 @@ public class NetEvents {
packet.itemid = (byte)item.id;
Net.send(packet, SendMode.udp);
}
public static void handleAdminSet(Player player, boolean admin){
PlayerAdminPacket packet = new PlayerAdminPacket();
packet.admin = admin;
packet.id = player.id;
player.isAdmin = admin;
Net.send(packet, SendMode.tcp);
}
public static void handleAdministerRequest(Player target, AdminAction action){
AdministerRequestPacket packet = new AdministerRequestPacket();
packet.id = target.id;
packet.action = action;
Net.send(packet, SendMode.tcp);
}
public static void handleTraceRequest(Player target){
if(Net.client()) {
handleAdministerRequest(target, AdminAction.trace);
}else{
ui.traces.show(target, netServer.admins.getTrace(Net.getConnection(target.clientid).address));
}
}
}

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.utils.ByteArray;
import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.game.GameMode;
import io.anuke.mindustry.resource.Upgrade;
import io.anuke.mindustry.resource.Weapon;
@@ -100,7 +101,7 @@ public class NetworkIO {
}
}
public static void writeWorld(int playerID, ByteArray upgrades, OutputStream os){
public static void writeWorld(Player player, ByteArray upgrades, OutputStream os){
try(DataOutputStream stream = new DataOutputStream(os)){
@@ -116,7 +117,8 @@ public class NetworkIO {
stream.writeInt(state.enemies); //enemy amount
stream.writeBoolean(state.friendlyFire); //friendly fire state
stream.writeInt(playerID); //player remap ID
stream.writeInt(player.id); //player remap ID
stream.writeBoolean(player.isAdmin);
//--INVENTORY--
@@ -246,6 +248,7 @@ public class NetworkIO {
state.friendlyFire = friendlyfire;
int pid = stream.readInt();
boolean admin = stream.readBoolean();
//inventory
for(int i = 0; i < state.inventory.getItems().length; i ++){
@@ -268,6 +271,7 @@ public class NetworkIO {
Entities.clear();
player.id = pid;
player.isAdmin = admin;
player.add();
//map

View File

@@ -8,6 +8,7 @@ import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Packet.ImportantPacket;
import io.anuke.mindustry.net.Packet.UnimportantPacket;
import io.anuke.mindustry.resource.Item;
import io.anuke.mindustry.world.Block;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup;
@@ -371,7 +372,7 @@ public class Packets {
}
public enum KickReason{
kick, invalidPassword, clientOutdated, serverOutdated
kick, invalidPassword, clientOutdated, serverOutdated, banned
}
public static class UpgradePacket implements Packet{
@@ -558,4 +559,97 @@ public class Packets {
itemid = buffer.get();
}
}
public static class NetErrorPacket implements Packet{
public String message;
@Override
public void write(ByteBuffer buffer) {
buffer.putShort((short)message.getBytes().length);
buffer.put(message.getBytes());
}
@Override
public void read(ByteBuffer buffer) {
short length = buffer.getShort();
byte[] bytes = new byte[length];
buffer.get(bytes);
message = new String(bytes);
}
}
public static class PlayerAdminPacket implements Packet{
public boolean admin;
public int id;
@Override
public void write(ByteBuffer buffer) {
buffer.put(admin ? (byte)1 : 0);
buffer.putInt(id);
}
@Override
public void read(ByteBuffer buffer) {
admin = buffer.get() == 1;
id = buffer.getInt();
}
}
public static class AdministerRequestPacket implements Packet{
public AdminAction action;
public int id;
@Override
public void write(ByteBuffer buffer) {
buffer.put((byte)action.ordinal());
buffer.putInt(id);
}
@Override
public void read(ByteBuffer buffer) {
action = AdminAction.values()[buffer.get()];
id = buffer.getInt();
}
}
public enum AdminAction{
kick, ban, trace
}
public static class TracePacket implements Packet{
public TraceInfo info;
@Override
public void write(ByteBuffer buffer) {
buffer.putInt(info.playerid);
buffer.putShort((short)info.ip.getBytes().length);
buffer.put(info.ip.getBytes());
buffer.put(info.modclient ? (byte)1 : 0);
buffer.putInt(info.totalBlocksBroken);
buffer.putInt(info.structureBlocksBroken);
buffer.putInt(info.lastBlockBroken.id);
buffer.putInt(info.totalBlocksPlaced);
buffer.putInt(info.lastBlockPlaced.id);
}
@Override
public void read(ByteBuffer buffer) {
int id = buffer.getInt();
short iplen = buffer.getShort();
byte[] ipb = new byte[iplen];
buffer.get(ipb);
info = new TraceInfo(new String(ipb));
info.playerid = id;
info.modclient = buffer.get() == 1;
info.totalBlocksBroken = buffer.getInt();
info.structureBlocksBroken = buffer.getInt();
info.lastBlockBroken = Block.getByID(buffer.getInt());
info.totalBlocksPlaced = buffer.getInt();
info.lastBlockPlaced = Block.getByID(buffer.getInt());
}
}
}

View File

@@ -40,7 +40,11 @@ public class Registrator {
EntitySpawnPacket.class,
ItemTransferPacket.class,
ItemSetPacket.class,
ItemOffloadPacket.class
ItemOffloadPacket.class,
NetErrorPacket.class,
PlayerAdminPacket.class,
AdministerRequestPacket.class,
TracePacket.class,
};
private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>();

View File

@@ -0,0 +1,21 @@
package io.anuke.mindustry.net;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.blocks.Blocks;
public class TraceInfo {
public int playerid;
public String ip;
public boolean modclient;
public int totalBlocksBroken;
public int structureBlocksBroken;
public Block lastBlockBroken = Blocks.air;
public int totalBlocksPlaced;
public Block lastBlockPlaced = Blocks.air;
public TraceInfo(String ip){
this.ip = ip;
}
}

View File

@@ -0,0 +1,65 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.NetEvents;
import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.layout.Table;
import static io.anuke.mindustry.Vars.*;
public class AdminsDialog extends FloatingDialog {
public AdminsDialog(){
super("$text.server.admins");
addCloseButton();
setup();
shown(this::setup);
}
private void setup(){
content().clear();
if(gwt) return;
float w = 400f, h = 80f;
Table table = new Table();
ScrollPane pane = new ScrollPane(table, "clear");
pane.setFadeScrollBars(false);
if(netServer.admins.getAdmins().size == 0){
table.add("$text.server.admins.none");
}
for(String ip : netServer.admins.getAdmins()){
Table res = new Table("button");
res.margin(14f);
res.labelWrap("[LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f);
res.add().growX();
res.addImageButton("icon-cancel", 14*3, () -> {
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
netServer.admins.unAdminPlayer(ip);
for(Player player : playerGroup.all()){
NetConnection c = Net.getConnection(player.clientid);
if(c != null){
NetEvents.handleAdminSet(player, false);
break;
}
}
setup();
});
}).size(h).pad(-14f);
table.add(res).width(w).height(h);
table.row();
}
content().add(pane);
}
}

View File

@@ -0,0 +1,55 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.layout.Table;
import static io.anuke.mindustry.Vars.*;
public class BansDialog extends FloatingDialog {
public BansDialog(){
super("$text.server.bans");
addCloseButton();
setup();
shown(this::setup);
}
private void setup(){
content().clear();
if(gwt) return;
float w = 400f, h = 80f;
Table table = new Table();
ScrollPane pane = new ScrollPane(table, "clear");
pane.setFadeScrollBars(false);
if(netServer.admins.getBanned().size == 0){
table.add("$text.server.bans.none");
}
for(String ip : netServer.admins.getBanned()){
Table res = new Table("button");
res.margin(14f);
res.labelWrap("IP: [LIGHT_GRAY]" + ip + "\n[]Name: [LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f);
res.add().growX();
res.addImageButton("icon-cancel", 14*3, () -> {
ui.showConfirm("$text.confirm", "$text.confirmunban", () -> {
netServer.admins.unbanPlayer(ip);
setup();
});
}).size(h).pad(-14f);
table.add(res).width(w).height(h);
table.row();
}
content().add(pane);
}
}

View File

@@ -14,7 +14,6 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.player;
import static io.anuke.mindustry.Vars.ui;
//TODO add port specification
public class HostDialog extends FloatingDialog{
float w = 300;
@@ -31,13 +30,13 @@ public class HostDialog extends FloatingDialog{
Settings.put("name", text);
Settings.save();
ui.listfrag.rebuild();
}).grow().pad(8);
}).grow().pad(8).get().setMaxLength(40);
ImageButton button = t.addImageButton("white", 40, () -> {
new ColorPickDialog().show(color -> {
player.color.set(color);
Settings.putInt("color", Color.rgba8888(color));
Settings.save();;
Settings.save();
});
}).size(50f, 54f).get();
button.update(() -> button.getStyle().imageUpColor = player.getColor());
@@ -50,6 +49,7 @@ public class HostDialog extends FloatingDialog{
Timers.runTask(5f, () -> {
try{
Net.host(Vars.port);
player.isAdmin = true;
}catch (IOException e){
ui.showError(Bundles.format("text.server.error", Strings.parseException(e, false)));
}
@@ -58,19 +58,4 @@ public class HostDialog extends FloatingDialog{
});
}).width(w).height(70f);
}
/*
showTextInput("$text.hostserver", "$text.server.port", Vars.port + "", new DigitsOnlyFilter(), text -> {
int result = Strings.parseInt(text);
if(result == Integer.MIN_VALUE || result >= 65535){
ui.showError("$text.server.invalidport");
}else{
try{
Net.host(result);
}catch (IOException e){
ui.showError(Bundles.format("text.server.error", Strings.parseException(e, false)));
}
}
});
*/
}

View File

@@ -56,7 +56,6 @@ public class JoinDialog extends FloatingDialog {
setupRemote();
refreshRemote();
}else{
//renaming.port = Strings.parseInt(Settings.getString("port"));
renaming.ip = Settings.getString("ip");
saveServers();
setupRemote();
@@ -85,7 +84,7 @@ public class JoinDialog extends FloatingDialog {
TextButton button = buttons[0] = remote.addButton("[accent]"+server.ip, "clear", () -> {
if(!buttons[0].childrenPressed()) connect(server.ip, Vars.port);
}).width(w).height(120f).pad(4f).get();
}).width(w).height(140f).pad(4f).get();
button.getLabel().setWrap(true);
@@ -134,10 +133,14 @@ public class JoinDialog extends FloatingDialog {
Net.pingHost(server.ip, server.port, host -> {
server.content.clear();
server.content.add("[lightgray]" + Bundles.format("text.server.hostname", host.name)).pad(4);
server.content.add("[lightgray]" + Bundles.format("text.server.hostname", host.name)).left();
server.content.row();
server.content.add("[lightgray]" + (host.players != 1 ? Bundles.format("text.players", host.players) :
Bundles.format("text.players.single", host.players)));
Bundles.format("text.players.single", host.players))).left();
server.content.row();
server.content.add("[lightgray]" + Bundles.format("text.save.map", host.mapname)).left();
server.content.row();
server.content.add("[lightgray]" + Bundles.format("text.save.wave", host.wave)).left();
}, e -> {
server.content.clear();
server.content.add("$text.host.invalid");
@@ -175,7 +178,7 @@ public class JoinDialog extends FloatingDialog {
Vars.player.name = text;
Settings.put("name", text);
Settings.save();
}).grow().pad(8);
}).grow().pad(8).get().setMaxLength(40);
ImageButton button = t.addImageButton("white", 40, () -> {
new ColorPickDialog().show(color -> {

View File

@@ -32,18 +32,18 @@ public class SettingsMenuDialog extends SettingsDialog{
public SettingsMenuDialog(){
setStyle(Core.skin.get("dialog", WindowStyle.class));
hidden(()->{
hidden(() -> {
if(!state.is(State.menu)){
if(!wasPaused || Net.active())
state.set(State.playing);
}
});
shown(()->{
shown(() -> {
if(!state.is(State.menu)){
wasPaused = state.is(State.paused);
if(menu.getScene() != null){
wasPaused = ((PausedDialog)menu).wasPaused;
if(ui.paused.getScene() != null){
wasPaused = ui.paused.wasPaused;
}
if(!Net.active()) state.set(State.paused);
ui.paused.hide();

View File

@@ -0,0 +1,53 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.TraceInfo;
import io.anuke.ucore.scene.ui.layout.Table;
import io.anuke.ucore.util.Bundles;
public class TraceDialog extends FloatingDialog {
public TraceDialog(){
super("$text.trace");
addCloseButton();
}
public void show(Player player, TraceInfo info){
content().clear();
Table table = new Table("button");
table.margin(14);
table.defaults().pad(1);
table.defaults().left();
table.add(Bundles.format("text.trace.playername", player.name));
table.row();
table.add(Bundles.format("text.trace.ip", info.ip));
table.row();
table.add(Bundles.format("text.trace.modclient", info.modclient));
table.row();
table.add().pad(5);
table.row();
table.add(Bundles.format("text.trace.totalblocksbroken", info.totalBlocksBroken));
table.row();
table.add(Bundles.format("text.trace.structureblocksbroken", info.structureBlocksBroken));
table.row();
table.add(Bundles.format("text.trace.lastblockbroken", info.lastBlockBroken.formalName));
table.row();
table.add().pad(5);
table.row();
table.add(Bundles.format("text.trace.totalblocksplaced", info.totalBlocksPlaced));
table.row();
table.add(Bundles.format("text.trace.lastblockplaced", info.lastBlockPlaced.formalName));
table.row();
content().add(table);
show();
}
}

View File

@@ -24,13 +24,14 @@ public class BackgroundFragment implements Fragment {
Core.batch.draw(back, w/2 - back.getRegionWidth()*backscl/2 +240f, h/2 - back.getRegionHeight()*backscl/2 + 250f,
back.getRegionWidth()*backscl, back.getRegionHeight()*backscl);
float logoscl = (int)Unit.dp.scl(7);
boolean portrait = Gdx.graphics.getWidth() < Gdx.graphics.getHeight();
float logoscl = (int)Unit.dp.scl(7) * (portrait ? 5f/7f : 1f);
TextureRegion logo = Core.skin.getRegion("logotext");
float logow = logo.getRegionWidth()*logoscl;
float logoh = logo.getRegionHeight()*logoscl;
Draw.color();
Core.batch.draw(logo, w/2 - logow/2, h - logoh + 15, logow, logoh);
Core.batch.draw(logo, w/2 - logow/2, h - logoh + 15 + (portrait ? -Unit.dp.scl(30f) : 0f), logow, logoh);
}).visible(() -> state.is(State.menu)).grow();
}
}

View File

@@ -57,11 +57,22 @@ public class DebugFragment implements Fragment {
row();
new button("wave", () -> state.wavetime = 0f);
row();
new button("time 0", () -> Timers.resetTime(0f));
row();
new button("time max", () -> Timers.resetTime(1080000 - 60*10));
row();
new button("clear", () -> {
enemyGroup.clear();
state.enemies = 0;
netClient.clearRecieved();
});
row();
new button("spawn", () -> {
for(int i = 0; i < 30; i ++){
new Enemy(EnemyTypes.healer).set(player.x + Mathf.range(50f), player.y + Mathf.range(50f)).add();
}
});
row();
}}.end();
row();

View File

@@ -16,10 +16,10 @@ import static io.anuke.mindustry.Vars.*;
public class MenuFragment implements Fragment{
public void build(){
if(!android){
//menu table
new table(){{
new table(){{
visible(() -> state.is(State.menu));
if(!android){
new table(){{
PressGroup group = new PressGroup();
@@ -53,37 +53,32 @@ public class MenuFragment implements Fragment{
}
get().margin(16);
}}.end();
visible(() -> state.is(State.menu));
}}.end();
}else{
new table(){{
new table(){{
}else {
new table() {{
defaults().size(120f).pad(5);
float isize = 14f*4;
float isize = 14f * 4;
new imagebutton("icon-play-2", isize, ui.levels::show).text("$text.play").padTop(4f);
new imagebutton("icon-tutorial", isize, () -> control.playMap(world.maps().getMap("tutorial"))).text("$text.tutorial").padTop(4f);
new imagebutton("icon-load", isize, ui.load::show).text("$text.load").padTop(4f);
new imagebutton("icon-add", isize, ui.join::show).text("$text.joingame").padTop(4f);
row();
new imagebutton("icon-editor", isize, ui.editor::show).text("$text.editor").padTop(4f);
new imagebutton("icon-tools", isize, ui.settings::show).text("$text.settings").padTop(4f);
new imagebutton("icon-info", isize, ui.about::show).text("$text.about.button").padTop(4f);
new imagebutton("icon-donate", isize, Platform.instance::openDonations).text("$text.donate").padTop(4f);
visible(() -> state.is(State.menu));
}}.end();
}}.end();
}
}
}}.end();
//extra icons in top right
new table(){{

View File

@@ -214,6 +214,7 @@ public class PlacementFragment implements Fragment{
breaktable.getParent().swapActor(breaktable, next);
if(!show){
control.input().breakMode = PlaceMode.none;
breaktable.actions(Actions.translateBy(-breaktable.getWidth() - 5, 0, dur, in), Actions.call(() -> shown = false));
}else{
shown = true;

View File

@@ -1,16 +1,21 @@
package io.anuke.mindustry.ui.fragments;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.NetEvents;
import io.anuke.mindustry.net.Packets.AdminAction;
import io.anuke.mindustry.net.Packets.KickReason;
import io.anuke.mindustry.ui.BorderImage;
import io.anuke.ucore.core.Inputs;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.scene.Element;
import io.anuke.ucore.scene.builders.button;
import io.anuke.ucore.scene.builders.label;
import io.anuke.ucore.scene.builders.table;
import io.anuke.ucore.scene.event.Touchable;
import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.layout.Stack;
import io.anuke.ucore.scene.ui.layout.Table;
@@ -40,10 +45,20 @@ public class PlayerListFragment implements Fragment{
row();
new table("pane"){{
margin(12f);
get().addCheck("$text.server.friendlyfire", b -> {
state.friendlyFire = b;
NetEvents.handleFriendlyFireChange(b);
}).growX().update(i -> i.setChecked(state.friendlyFire)).disabled(b -> Net.client());
}).growX().update(i -> i.setChecked(state.friendlyFire)).disabled(b -> Net.client()).padRight(5);
new button("$text.server.bans", () -> {
ui.bans.show();
}).padTop(-12).padBottom(-12).fillY().cell.disabled(b -> Net.client());
new button("$text.server.admins", () -> {
ui.admins.show();
}).padTop(-12).padBottom(-12).padRight(-12).fillY().cell.disabled(b -> Net.client());
}}.pad(10f).growX().end();
}}.end();
@@ -69,9 +84,13 @@ public class PlayerListFragment implements Fragment{
public void rebuild(){
content.clear();
float h = 60f;
float h = 74f;
for(Player player : playerGroup.all()){
NetConnection connection = gwt ? null : Net.getConnection(player.clientid);
if(connection == null && Net.server() && !player.isLocal) continue;
Table button = new Table("button");
button.left();
button.margin(5).marginBottom(10);
@@ -93,19 +112,68 @@ public class PlayerListFragment implements Fragment{
}
});
}
button.add(stack).size(h);
button.add("[#" + player.getColor().toString().toUpperCase() + "]" + player.name).pad(10);
button.labelWrap("[#" + player.getColor().toString().toUpperCase() + "]" + player.name).width(170f).pad(10);
button.add().grow();
if(Net.server() && !player.isLocal){
button.addImage("icon-admin").size(14*2).visible(() -> player.isAdmin && !(!player.isLocal && Net.server())).padRight(5);
if((Net.server() || Vars.player.isAdmin) && !player.isLocal && (!player.isAdmin || Net.server())){
button.add().growY();
button.addImageButton("icon-cancel", 14*3, () ->
Net.kickConnection(player.clientid, KickReason.kick)
).pad(-5).padBottom(-10).size(h+10, h+14);
float bs = (h + 14)/2f;
button.table(t -> {
t.defaults().size(bs - 1, bs + 3);
t.addImageButton("icon-ban", 14*2, () -> {
ui.showConfirm("$text.confirm", "$text.confirmban", () -> {
if(Net.server()) {
netServer.admins.banPlayer(connection.address);
Net.kickConnection(player.clientid, KickReason.banned);
}else{
NetEvents.handleAdministerRequest(player, AdminAction.ban);
}
});
}).padBottom(-5.1f);
t.addImageButton("icon-cancel", 14*2, () -> {
if(Net.server()) {
Net.kickConnection(player.clientid, KickReason.kick);
}else{
NetEvents.handleAdministerRequest(player, AdminAction.kick);
}
}).padBottom(-5.1f);
t.row();
t.addImageButton("icon-admin", "toggle", 14*2, () -> {
if(Net.client()) return;
if(netServer.admins.isAdmin(connection.address)){
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
netServer.admins.unAdminPlayer(connection.address);
NetEvents.handleAdminSet(player, false);
});
}else{
ui.showConfirm("$text.confirm", "$text.confirmadmin", () -> {
netServer.admins.adminPlayer(connection.address);
NetEvents.handleAdminSet(player, true);
});
}
}).update(b ->{
b.setChecked(player.isAdmin);
b.setDisabled(Net.client());
}).get().setTouchable(() -> Net.client() ? Touchable.disabled : Touchable.enabled);
t.addImageButton("icon-zoom-small", 14*2, () -> NetEvents.handleTraceRequest(player));
}).padRight(12).padTop(-5).padLeft(0).padBottom(-10).size(bs + 10f, bs);
}
content.add(button).padBottom(-5).width(350f);
content.add(button).padBottom(-6).width(350f).maxHeight(h + 14);
content.row();
}

View File

@@ -0,0 +1,166 @@
package io.anuke.mindustry.world;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.game.SpawnPoint;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.resource.ItemStack;
import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.ProductionBlocks;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Sounds;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.SolidEntity;
import static io.anuke.mindustry.Vars.*;
public class Placement {
private static final Rectangle rect = new Rectangle();
/**Returns block type that was broken, or null if unsuccesful.*/
public static Block breakBlock(int x, int y, boolean effect, boolean sound){
Tile tile = world.tile(x, y);
if(tile == null) return null;
Block block = tile.isLinked() ? tile.getLinked().block() : tile.block();
Recipe result = Recipes.getByResult(block);
if(result != null){
for(ItemStack stack : result.requirements){
state.inventory.addItem(stack.item, (int)(stack.amount * breakDropAmount));
}
}
if(tile.block().drops != null){
state.inventory.addItem(tile.block().drops.item, tile.block().drops.amount);
}
if(sound) Sounds.play("break");
if(!tile.block().isMultiblock() && !tile.isLinked()){
tile.setBlock(Blocks.air);
if(effect) Effects.effect(Fx.breakBlock, tile.worldx(), tile.worldy());
}else{
Tile target = tile.isLinked() ? tile.getLinked() : tile;
Array<Tile> removals = target.getLinkedTiles();
for(Tile toremove : removals){
//note that setting a new block automatically unlinks it
toremove.setBlock(Blocks.air);
if(effect) Effects.effect(Fx.breakBlock, toremove.worldx(), toremove.worldy());
}
}
return block;
}
public static void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
Tile tile = world.tile(x, y);
//just in case
if(tile == null) return;
tile.setBlock(result, rotation);
if(result.isMultiblock()){
int offsetx = -(result.width-1)/2;
int offsety = -(result.height-1)/2;
for(int dx = 0; dx < result.width; dx ++){
for(int dy = 0; dy < result.height; dy ++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(!(worldx == x && worldy == y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null)
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
}
if(effects) Effects.effect(Fx.place, worldx * tilesize, worldy * tilesize);
}
}
}else if(effects) Effects.effect(Fx.place, x * tilesize, y * tilesize);
if(effects && sound) Sounds.play("place");
}
public static boolean validPlace(int x, int y, Block type){
for(int i = 0; i < world.getSpawns().size; i ++){
SpawnPoint spawn = world.getSpawns().get(i);
if(Vector2.dst(x * tilesize, y * tilesize, spawn.start.worldx(), spawn.start.worldy()) < enemyspawnspace){
return false;
}
}
Recipe recipe = Recipes.getByResult(type);
if(recipe == null || !state.inventory.hasItems(recipe.requirements)){
return false;
}
rect.setSize(type.width * tilesize, type.height * tilesize);
Vector2 offset = type.getPlaceOffset();
rect.setCenter(offset.x + x * tilesize, offset.y + y * tilesize);
synchronized (Entities.entityLock) {
for (SolidEntity e : Entities.getNearby(enemyGroup, x * tilesize, y * tilesize, tilesize * 2f)) {
if (e == null) continue; //not sure why this happens?
Rectangle rect = e.hitbox.getRect(e.x, e.y);
if (Placement.rect.overlaps(rect)) {
return false;
}
}
}
if(type.solid || type.solidifes) {
for (Player player : playerGroup.all()) {
if (!player.isAndroid && rect.overlaps(player.hitbox.getRect(player.x, player.y))) {
return false;
}
}
}
Tile tile = world.tile(x, y);
if(tile == null || (isSpawnPoint(tile) && (type.solidifes || type.solid))) return false;
if(type.isMultiblock()){
int offsetx = -(type.width-1)/2;
int offsety = -(type.height-1)/2;
for(int dx = 0; dx < type.width; dx ++){
for(int dy = 0; dy < type.height; dy ++){
Tile other = world.tile(x + dx + offsetx, y + dy + offsety);
if(other == null || (other.block() != Blocks.air && !other.block().alwaysReplace) || isSpawnPoint(other)){
return false;
}
}
}
return true;
}else {
return tile.block() != type
&& (type.canReplace(tile.block()) || tile.block().alwaysReplace)
&& tile.block().isMultiblock() == type.isMultiblock() || tile.block() == Blocks.air;
}
}
public static boolean isSpawnPoint(Tile tile){
return tile != null && tile.x == world.getCore().x && tile.y == world.getCore().y - 2;
}
public static boolean validBreak(int x, int y){
Tile tile = world.tile(x, y);
if(tile == null || tile.block() == ProductionBlocks.core) return false;
if(tile.isLinked() && tile.getLinked().block() == ProductionBlocks.core){
return false;
}
return tile.breakable();
}
}

View File

@@ -43,13 +43,14 @@ public class LaserTurret extends PowerTurret{
@Override
public void drawLayer2(Tile tile){
TurretEntity entity = tile.entity();
Enemy enemy = entity.target;
if(entity.target != null &&
Angles.angleDist(entity.rotation, Angles.angle(tile.drawx(), tile.drawy(), entity.target.x, entity.target.y)) <= cone){
if(enemy != null &&
Angles.angleDist(entity.rotation, Angles.angle(tile.drawx(), tile.drawy(), enemy.x, enemy.y)) <= cone){
float len = 4f;
float x = tile.drawx() + Angles.trnsx(entity.rotation, len), y = tile.drawy() + Angles.trnsy(entity.rotation, len);
float x2 = entity.target.x, y2 = entity.target.y;
float x2 = enemy.x, y2 = enemy.y;
float lighten = (MathUtils.sin(Timers.time()/1.2f) + 1f) / 10f;

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.world.blocks.types.defense;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.world.Layer;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.core.Timers;
@@ -80,9 +81,10 @@ public class RepairTurret extends PowerTurret{
@Override
public void drawLayer2(Tile tile){
PowerTurretEntity entity = tile.entity();
TileEntity target = entity.blockTarget;
if(entity.power >= powerUsed && entity.blockTarget != null && Angles.angleDist(entity.angleTo(entity.blockTarget), entity.rotation) < 10){
Tile targetTile = entity.blockTarget.tile;
if(entity.power >= powerUsed && target != null && Angles.angleDist(entity.angleTo(target), entity.rotation) < 10){
Tile targetTile = target.tile;
float len = 4f;
float x = tile.drawx() + Angles.trnsx(entity.rotation, len), y = tile.drawy() + Angles.trnsy(entity.rotation, len);

View File

@@ -84,6 +84,7 @@ public class Junction extends Block{
return new JunctionEntity();
}
@Override
public Array<Object> getDebugInfo(Tile tile){
JunctionEntity entity = tile.entity();
Array<Object> arr = super.getDebugInfo(tile);

View File

@@ -1,5 +1,6 @@
package io.anuke.mindustry.world.blocks.types.distribution;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.NumberUtils;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.resource.Item;
@@ -90,10 +91,35 @@ public class TunnelConveyor extends Block{
return new TunnelEntity();
}
@Override
public Array<Object> getDebugInfo(Tile tile){
TunnelEntity entity = tile.entity();
Array<Object> arr = super.getDebugInfo(tile);
for(int i = 0; i < 4; i ++){
arr.add("nearby." + i);
arr.add(tile.getNearby(i));
}
arr.add("buffer");
arr.add(entity.index);
for(int i = 0; i < entity.index; i++){
long l = entity.items[i];
float time = NumberUtils.intBitsToFloat(Bits.getLeftInt(l));
Item item = Item.getByID(Bits.getRightInt(l));
Tile dest = getDestTunnel(tile, item);
arr.add(" buffer.item");
arr.add(time + " | " + item.name + " | " + ( dest == null ? "no dest" : dest.block() + ":" + dest.floor()));
}
return arr;
}
Tile getDestTunnel(Tile tile, Item item){
Tile dest = tile;
int rel = (tile.getRotation() + 2)%4;
for(int i = 0; i < maxdist; i ++){
if(dest == null) return null;
dest = dest.getNearby(rel);
if(dest != null && dest.block() instanceof TunnelConveyor && dest.getRotation() == rel
&& dest.getNearby(rel) != null