diff --git a/build.gradle b/build.gradle index f051cf3437..adc3e55d78 100644 --- a/build.gradle +++ b/build.gradle @@ -312,6 +312,5 @@ project(":net"){ dependencies{ compile project(":core") compile "org.lz4:lz4-java:1.4.1" - compile 'com.github.Anuken:WaifUPnP:05eb46bc577fd7674596946ba288c96c0cedd893' } } diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 18d41588f2..b4fbdc2151 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -350,35 +350,37 @@ public class Control implements ApplicationListener{ //display UI scale changed dialog if(Core.settings.getBool("uiscalechanged", false)){ - FloatingDialog dialog = new FloatingDialog("$confirm"); + Core.app.post(() -> Core.app.post(() -> { + FloatingDialog dialog = new FloatingDialog("$confirm"); + dialog.setFillParent(true); - float[] countdown = {60 * 11}; - Runnable exit = () -> { - Core.settings.put("uiscale", 100); - Core.settings.put("uiscalechanged", false); - settings.save(); - dialog.hide(); - Core.app.exit(); - }; + float[] countdown = {60 * 11}; + Runnable exit = () -> { + Core.settings.put("uiscale", 100); + Core.settings.put("uiscalechanged", false); + settings.save(); + dialog.hide(); + Core.app.exit(); + }; - dialog.setFillParent(false); - dialog.cont.label(() -> { - if(countdown[0] <= 0){ - exit.run(); - } - return Core.bundle.format("uiscale.reset", (int)((countdown[0] -= Time.delta()) / 60f)); - }).pad(10f).expand().left(); + dialog.cont.label(() -> { + if(countdown[0] <= 0){ + exit.run(); + } + return Core.bundle.format("uiscale.reset", (int)((countdown[0] -= Time.delta()) / 60f)); + }).pad(10f).expand().center(); - dialog.buttons.defaults().size(200f, 60f); - dialog.buttons.addButton("$uiscale.cancel", exit); + dialog.buttons.defaults().size(200f, 60f); + dialog.buttons.addButton("$uiscale.cancel", exit); - dialog.buttons.addButton("$ok", () -> { - Core.settings.put("uiscalechanged", false); - settings.save(); - dialog.hide(); - }); + dialog.buttons.addButton("$ok", () -> { + Core.settings.put("uiscalechanged", false); + settings.save(); + dialog.hide(); + }); - Core.app.post(dialog::show); + dialog.show(); + })); } } diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index b9c8510b02..ede04df856 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -40,7 +40,7 @@ import static io.anuke.mindustry.Vars.*; public class NetServer implements ApplicationListener{ public final static int maxSnapshotSize = 430; - private final static float serverSyncTime = 15, kickDuration = 30 * 1000; + private final static float serverSyncTime = 12, kickDuration = 30 * 1000; private final static Vector2 vector = new Vector2(); private final static Rectangle viewport = new Rectangle(); /** If a player goes away of their server-side coordinates by this distance, they get teleported back. */ diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index 55368d1d57..1b601d3d7f 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -1,33 +1,28 @@ package io.anuke.mindustry.entities.traits; import io.anuke.annotations.Annotations.*; -import io.anuke.arc.Core; -import io.anuke.arc.Events; -import io.anuke.arc.collection.Array; +import io.anuke.arc.*; import io.anuke.arc.collection.Queue; +import io.anuke.arc.collection.*; import io.anuke.arc.graphics.g2d.*; -import io.anuke.arc.math.Angles; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.math.geom.Vector2; -import io.anuke.arc.util.Time; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.content.Blocks; -import io.anuke.mindustry.entities.type.TileEntity; -import io.anuke.mindustry.entities.type.Unit; -import io.anuke.mindustry.game.EventType.BuildSelectEvent; -import io.anuke.mindustry.gen.Call; -import io.anuke.mindustry.graphics.Pal; -import io.anuke.mindustry.net.Net; +import io.anuke.arc.math.*; +import io.anuke.arc.math.geom.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.*; +import io.anuke.mindustry.content.*; +import io.anuke.mindustry.entities.type.*; +import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.gen.*; +import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.world.*; -import io.anuke.mindustry.world.blocks.BuildBlock; -import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity; +import io.anuke.mindustry.world.blocks.*; +import io.anuke.mindustry.world.blocks.BuildBlock.*; import java.io.*; -import java.util.Arrays; +import java.util.*; import static io.anuke.mindustry.Vars.*; -import static io.anuke.mindustry.entities.traits.BuilderTrait.BuildDataStatic.removal; -import static io.anuke.mindustry.entities.traits.BuilderTrait.BuildDataStatic.tmptr; +import static io.anuke.mindustry.entities.traits.BuilderTrait.BuildDataStatic.*; /** Interface for units that build things.*/ public interface BuilderTrait extends Entity, TeamTrait{ @@ -106,19 +101,14 @@ public interface BuilderTrait extends Entity, TeamTrait{ unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f); } - //progress is synced, thus not updated clientside - if(!Net.client()){ - //deconstructing is 2x as fast - if(current.breaking){ - entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); - }else{ - entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); - } - - current.progress = entity.progress(); + //deconstructing is 2x as fast + if(current.breaking){ + entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); }else{ - entity.progress = current.progress; + entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); } + + current.progress = entity.progress; } /** Returns the queue for storing build requests. */ @@ -174,7 +164,11 @@ public interface BuilderTrait extends Entity, TeamTrait{ if(applyChanges){ buildQueue().addLast(request); }else if(isBuilding()){ - buildRequest().progress = progress; + BuildRequest last = buildRequest(); + last.progress = progress; + if(last.tile() != null && last.tile().entity instanceof BuildEntity){ + ((BuildEntity)last.tile().entity).progress = progress; + } } } } diff --git a/core/src/io/anuke/mindustry/net/Interpolator.java b/core/src/io/anuke/mindustry/net/Interpolator.java index 0201f980ce..dbc9b422ca 100644 --- a/core/src/io/anuke/mindustry/net/Interpolator.java +++ b/core/src/io/anuke/mindustry/net/Interpolator.java @@ -1,14 +1,15 @@ package io.anuke.mindustry.net; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.math.geom.Vector2; -import io.anuke.arc.util.Time; +import io.anuke.arc.math.*; +import io.anuke.arc.math.geom.*; +import io.anuke.arc.util.*; public class Interpolator{ //used for movement public Vector2 target = new Vector2(); public Vector2 last = new Vector2(); public float[] targets = {}; + public float[] lasts = {}; public long lastUpdated, updateSpacing; //current state @@ -21,6 +22,12 @@ public class Interpolator{ lastUpdated = Time.millis(); targets = target1ds; + if(lasts.length != values.length){ + lasts = new float[values.length]; + } + for(int i = 0; i < values.length; i++){ + lasts[i] = values[i]; + } last.set(cx, cy); target.set(x, y); } @@ -46,8 +53,12 @@ public class Interpolator{ values = new float[targets.length]; } + if(lasts.length != targets.length){ + lasts = new float[targets.length]; + } + for(int i = 0; i < values.length; i++){ - values[i] = Mathf.slerp(values[i], targets[i], alpha); + values[i] = Mathf.slerp(lasts[i], targets[i], alpha); } }else{ pos.set(target); diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index d0965c3cb8..19f459ea60 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -162,8 +162,8 @@ public class Net{ /** * Returns a list of all connections IDs. */ - public static Array getConnections(){ - return (Array)serverProvider.getConnections(); + public static Iterable getConnections(){ + return (Iterable)serverProvider.getConnections(); } /** @@ -326,7 +326,7 @@ public class Net{ public static void dispose(){ if(clientProvider != null) clientProvider.dispose(); - if(serverProvider != null) serverProvider.dispose(); + if(serverProvider != null) serverProvider.close(); clientProvider = null; serverProvider = null; server = false; @@ -377,16 +377,53 @@ public class Net{ void host(int port) throws IOException; /** Sends a large stream of data to a specific client. */ - void sendStream(int id, Streamable stream); + 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; - /** Send an object to everyone connected. */ - void send(Object object, SendMode mode); + while(stream.stream.available() > 0){ + byte[] bytes = new byte[Math.min(512, stream.stream.available())]; + stream.stream.read(bytes); - /** Send an object to a specific client ID. */ - void sendTo(int id, Object object, SendMode mode); + StreamChunk chunk = new StreamChunk(); + chunk.id = cid; + chunk.data = bytes; + connection.send(chunk, SendMode.tcp); + } + }catch(IOException e){ + throw new RuntimeException(e); + } + } - /** Send an object to everyone except a client ID. */ - void sendExcept(int id, Object object, SendMode mode); + 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(); @@ -395,12 +432,9 @@ public class Net{ byte[] compressSnapshot(byte[] input); /** Return all connected users. */ - Array getConnections(); + Iterable getConnections(); /** Returns a connection by ID. */ NetConnection getByID(int id); - - /** Close all connections. */ - void dispose(); } } diff --git a/core/src/io/anuke/mindustry/net/NetConnection.java b/core/src/io/anuke/mindustry/net/NetConnection.java index 2bb53fb9f7..ba2a004a03 100644 --- a/core/src/io/anuke/mindustry/net/NetConnection.java +++ b/core/src/io/anuke/mindustry/net/NetConnection.java @@ -3,6 +3,8 @@ package io.anuke.mindustry.net; import io.anuke.mindustry.net.Net.SendMode; public abstract class NetConnection{ + private static int lastID; + public final int id; public final String address; @@ -18,8 +20,9 @@ public abstract class NetConnection{ public boolean hasBegunConnecting = false; public float viewWidth, viewHeight, viewX, viewY; - public NetConnection(int id, String address){ - this.id = id; + /** 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; } diff --git a/net/src/io/anuke/mindustry/net/ArcNetClient.java b/net/src/io/anuke/mindustry/net/ArcNetClient.java index 485235724f..618aea5eb4 100644 --- a/net/src/io/anuke/mindustry/net/ArcNetClient.java +++ b/net/src/io/anuke/mindustry/net/ArcNetClient.java @@ -4,6 +4,7 @@ import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.net.*; +import io.anuke.arc.util.async.*; import io.anuke.arc.util.pooling.*; import io.anuke.mindustry.net.Net.*; import io.anuke.mindustry.net.Packets.*; @@ -84,7 +85,7 @@ public class ArcNetClient implements ClientProvider{ @Override public void connect(String ip, int port, Runnable success){ - runAsync(() -> { + Threads.daemon(() -> { try{ //just in case client.stop(); @@ -99,7 +100,7 @@ public class ArcNetClient implements ClientProvider{ updateThread.setDaemon(true); updateThread.start(); - client.connect(5000, ip, port, port); + client.connect(5000, ip, port); success.run(); }catch(Exception e){ handleException(e); @@ -115,11 +116,7 @@ public class ArcNetClient implements ClientProvider{ @Override public void send(Object object, SendMode mode){ try{ - if(mode == SendMode.tcp){ - client.sendTCP(object); - }else{ - client.sendUDP(object); - } + client.sendTCP(object); //sending things can cause an under/overflow, catch it and disconnect instead of crashing }catch(BufferOverflowException | BufferUnderflowException e){ Net.showError(e); @@ -140,7 +137,7 @@ public class ArcNetClient implements ClientProvider{ @Override public void pingHost(String address, int port, Consumer valid, Consumer invalid){ - runAsync(() -> { + Threads.daemon(() -> { try{ DatagramSocket socket = new DatagramSocket(); socket.send(new DatagramPacket(new byte[]{-2, 1}, 2, InetAddress.getByName(address), port)); @@ -189,12 +186,6 @@ public class ArcNetClient implements ClientProvider{ } } - private void runAsync(Runnable run){ - Thread thread = new Thread(run, "Client Async Run"); - thread.setDaemon(true); - thread.start(); - } - private void handleException(Exception e){ if(e instanceof ArcNetException){ Core.app.post(() -> Net.showError(new IOException("mismatch"))); diff --git a/net/src/io/anuke/mindustry/net/ArcNetServer.java b/net/src/io/anuke/mindustry/net/ArcNetServer.java index ed8496a8e2..08c571c119 100644 --- a/net/src/io/anuke/mindustry/net/ArcNetServer.java +++ b/net/src/io/anuke/mindustry/net/ArcNetServer.java @@ -1,11 +1,9 @@ package io.anuke.mindustry.net; -import com.dosse.upnp.*; import io.anuke.arc.*; -import io.anuke.arc.collection.*; import io.anuke.arc.net.*; import io.anuke.arc.util.*; -import io.anuke.mindustry.*; +import io.anuke.arc.util.async.*; import io.anuke.mindustry.net.Net.*; import io.anuke.mindustry.net.Packets.*; import net.jpountz.lz4.*; @@ -19,14 +17,10 @@ import static io.anuke.mindustry.Vars.*; public class ArcNetServer implements ServerProvider{ final Server server; - final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); - final CopyOnWriteArraySet missing = new CopyOnWriteArraySet<>(); - final Array array = new Array<>(); + final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor(); Thread serverThread; - int lastconnection = 0; - public ArcNetServer(){ server = new Server(4096 * 2, 4096, new PacketSerializer()); server.setMulticast(multicastGroup, multicastPort); @@ -42,7 +36,7 @@ public class ArcNetServer implements ServerProvider{ public void connected(Connection connection){ String ip = connection.getRemoteAddressTCP().getAddress().getHostAddress(); - KryoConnection kn = new KryoConnection(lastconnection++, ip, connection); + ArcConnection kn = new ArcConnection(ip, connection); Connect c = new Connect(); c.id = kn.id; @@ -56,7 +50,7 @@ public class ArcNetServer implements ServerProvider{ @Override public void disconnected(Connection connection){ - KryoConnection k = getByKryoID(connection.getID()); + ArcConnection k = getByKryoID(connection.getID()); if(k == null) return; Disconnect c = new Disconnect(); @@ -70,7 +64,7 @@ public class ArcNetServer implements ServerProvider{ @Override public void received(Connection connection, Object object){ - KryoConnection k = getByKryoID(connection.getID()); + ArcConnection k = getByKryoID(connection.getID()); if(object instanceof FrameworkMessage || k == null) return; Core.app.post(() -> { @@ -99,18 +93,14 @@ public class ArcNetServer implements ServerProvider{ } @Override - public Array getConnections(){ - array.clear(); - for(KryoConnection c : connections){ - array.add(c); - } - return array; + public Iterable getConnections(){ + return connections; } @Override - public KryoConnection getByID(int id){ + public ArcConnection getByID(int id){ for(int i = 0; i < connections.size(); i++){ - KryoConnection con = connections.get(i); + ArcConnection con = connections.get(i); if(con.id == id){ return con; } @@ -121,28 +111,14 @@ public class ArcNetServer implements ServerProvider{ @Override public void host(int port) throws IOException{ - //attempt to open default ports if they're not already open - //this only opens the default port due to security concerns (?) - if(port == Vars.port){ - async(() -> { - try{ - if(!UPnP.isMappedTCP(port)) UPnP.openPortTCP(port); - if(!UPnP.isMappedUDP(port)) UPnP.openPortUDP(port); - }catch(Throwable ignored){ - } - }); - } - - lastconnection = 0; connections.clear(); - missing.clear(); - server.bind(port, port); + server.bind(port); serverThread = new Thread(() -> { try{ server.run(); }catch(Throwable e){ - if(!(e instanceof ClosedSelectorException)) handleException(e); + if(!(e instanceof ClosedSelectorException)) Threads.throwAppException(e); } }, "Net Server"); serverThread.setDaemon(true); @@ -152,102 +128,12 @@ public class ArcNetServer implements ServerProvider{ @Override public void close(){ connections.clear(); - lastconnection = 0; - - async(server::stop); + Threads.daemon(server::stop); } - @Override - public void sendStream(int id, Streamable stream){ - KryoConnection connection = getByID(id); - if(connection == null) return; - try{ - - if(connection.connection != null){ - - connection.connection.addListener(new InputStreamSender(stream.stream, 512){ - int id; - - 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.connection.sendTCP(begin); - id = begin.id; - } - - 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. - } - }); - }else{ - 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); - } - } - - @Override - public void send(Object object, SendMode mode){ + ArcConnection getByKryoID(int id){ for(int i = 0; i < connections.size(); i++){ - connections.get(i).send(object, mode); - } - } - - @Override - public void sendTo(int id, Object object, SendMode mode){ - NetConnection conn = getByID(id); - if(conn == null){ - if(!missing.contains(id)) - Log.err("Failed to find connection with ID {0}.", id); - missing.add(id); - return; - } - conn.send(object, mode); - } - - @Override - public void sendExcept(int id, Object object, SendMode mode){ - for(int i = 0; i < connections.size(); i++){ - KryoConnection conn = connections.get(i); - if(conn.id != id) conn.send(object, mode); - } - } - - @Override - public void dispose(){ - close(); - } - - private void handleException(Throwable e){ - Time.run(0f, () -> { - throw new RuntimeException(e); - }); - } - - KryoConnection getByKryoID(int id){ - for(int i = 0; i < connections.size(); i++){ - KryoConnection con = connections.get(i); + ArcConnection con = connections.get(i); if(con.connection != null && con.connection.getID() == id){ return con; } @@ -256,17 +142,11 @@ public class ArcNetServer implements ServerProvider{ return null; } - void async(Runnable run){ - Thread thread = new Thread(run); - thread.setDaemon(true); - thread.start(); - } - - class KryoConnection extends NetConnection{ + class ArcConnection extends NetConnection{ public final Connection connection; - public KryoConnection(int id, String address, Connection connection){ - super(id, address); + public ArcConnection(String address, Connection connection){ + super(address); this.connection = connection; } @@ -278,17 +158,13 @@ public class ArcNetServer implements ServerProvider{ @Override public void send(Object object, SendMode mode){ try{ - if(mode == SendMode.tcp){ - connection.sendTCP(object); - }else{ - connection.sendUDP(object); - } + connection.sendTCP(object); }catch(Exception e){ Log.err(e); Log.info("Error sending packet. Disconnecting invalid client!"); connection.close(); - KryoConnection k = getByKryoID(connection.getID()); + ArcConnection k = getByKryoID(connection.getID()); if(k != null) connections.remove(k); } }