diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 44d8c4ff5a..4a1da71951 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -1,8 +1,12 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.reflect.ClassReflection; +import com.badlogic.gdx.utils.reflect.ReflectionException; +import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.traits.SyncTrait; import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net.SendMode; @@ -10,10 +14,17 @@ import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.Packets.*; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.Entities; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.io.ReusableByteArrayInputStream; +import io.anuke.ucore.io.delta.DEZDecoder; import io.anuke.ucore.modules.Module; import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Timer; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Arrays; + import static io.anuke.mindustry.Vars.*; public class NetClient extends Module { @@ -27,6 +38,15 @@ public class NetClient extends Module { private boolean quiet = false; /**Counter for data timeout.*/ private float timeoutTime = 0f; + /**Last snapshot recieved.*/ + private byte[] lastSnapshot; + /**Last snapshot ID recieved.*/ + private int lastSnapshotID = -1; + /**Decoder for uncompressing snapshots.*/ + private DEZDecoder decoder = new DEZDecoder(); + /**Byte stream for reading in snapshots.*/ + private ReusableByteArrayInputStream byteStream = new ReusableByteArrayInputStream(); + private DataInputStream dataStream = new DataInputStream(byteStream); public NetClient(){ @@ -135,6 +155,7 @@ public class NetClient extends Module { Player player = players[0]; ClientSnapshotPacket packet = new ClientSnapshotPacket(); + packet.lastSnapshot = lastSnapshotID; packet.player = player; Net.send(packet, SendMode.udp); } @@ -143,4 +164,73 @@ public class NetClient extends Module { Net.updatePing(); } } + + @Remote(one = true, all = false, unreliable = true) + public static void onSnapshot(byte[] snapshot, int snapshotID){ + + //skip snapshot IDs that have already been recieved + if(snapshotID == netClient.lastSnapshotID){ + return; + } + + try { + + byte[] result; + int length; + if (snapshotID == -1) { //-1 = fresh snapshot + result = snapshot; + length = snapshot.length; + netClient.lastSnapshot = snapshot; + } else { //otherwise, last snapshot must not be null, decode it + netClient.decoder.init(netClient.lastSnapshot, snapshot); + result = netClient.decoder.decode(); + length = netClient.decoder.getDecodedLength(); + //set last snapshot to a copy to prevent issues + netClient.lastSnapshot = Arrays.copyOf(result, length); + } + + //set stream bytes to begin write + netClient.byteStream.setBytes(result, 0, length); + + //get data input for reading from the stream + DataInputStream input = netClient.dataStream; + + byte totalGroups = input.readByte(); + //for each group... + for (int i = 0; i < totalGroups; i++) { + //read group info + byte groupID = input.readByte(); + short amount = input.readShort(); + long timestamp = input.readLong(); + + EntityGroup group = Entities.getGroup(groupID); + + //go through each entity + for (int j = 0; j < amount; j++) { + int id = input.readInt(); + + SyncTrait entity = (SyncTrait) group.getByID(id); + + //entity must not be added yet, so create it + if(entity == null){ + entity = (SyncTrait) ClassReflection.newInstance(group.getType()); //TODO solution without reflection? + entity.add(); + } + + //read the entity + entity.read(input, timestamp); + } + } + + //confirm that snapshot 0 has been recieved if this is the initial snapshot + if(snapshotID == -1){ + netClient.lastSnapshotID = 0; + }else{ //confirm that the snapshot has been recieved + netClient.lastSnapshotID = snapshotID; + } + + }catch (IOException | ReflectionException e){ + throw new RuntimeException(e); + } + } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index c6b6d3fb4f..61485c4062 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -23,7 +23,6 @@ import io.anuke.ucore.io.delta.ByteMatcherHash; import io.anuke.ucore.io.delta.DEZEncoder; import io.anuke.ucore.modules.Module; import io.anuke.ucore.util.Log; -import io.anuke.ucore.util.Timer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -43,7 +42,6 @@ public class NetServer extends Module{ /**Maps connection IDs to players.*/ private IntMap connections = new IntMap<>(); private boolean closing = false; - private Timer timer = new Timer(5); /**Stream for writing player sync data to.*/ private ByteArrayOutputStream syncStream = new ByteArrayOutputStream(); @@ -131,8 +129,8 @@ public class NetServer extends Module{ Platform.instance.updateRPC(); }); - - Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> {}); + //update last recieved snapshot based on client snapshot + Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> Net.getConnection(id).lastSnapshotID = packet.lastSnapshot); Net.handleServer(InvokePacket.class, (id, packet) -> RemoteReadServer.readPacket(packet.writeBuffer, packet.type, connections.get(id))); } @@ -190,6 +188,19 @@ public class NetServer extends Module{ //iterate through each player for (Player player : connections.values()) { + NetConnection connection = Net.getConnection(player.clientid); + + if(!player.timer.get(Player.timeSync, serverSyncTime)) continue; + + //if the player hasn't acknolwedged that it has recieved the packet, send the same thing again + if(connection.lastSentSnapshotID > connection.lastSnapshotID){ + Call.onSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID); + return; + }else{ + //set up last confirmed snapshot to the last one that was sent, otherwise + connection.lastSnapshot = connection.lastSentSnapshot; + } + //reset stream to begin writing syncStream.reset(); @@ -207,24 +218,32 @@ public class NetServer extends Module{ //TODO range-check sync positions? if (group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; + //make sure mapping is enabled for this group + if(!group.mappingEnabled()){ + throw new RuntimeException("Entity group '" + group.getType() + "' contains SyncTrait entities, yet mapping is not enabled. In order for syncing to work, you must enable mapping for this group."); + } + //write group ID + group size dataStream.writeByte(group.getID()); dataStream.writeShort(group.size()); + //write timestamp + dataStream.writeLong(TimeUtils.millis()); for(Entity entity : group.all()){ //write all entities now + dataStream.writeInt(entity.getID()); ((SyncTrait)entity).write(dataStream); } } - NetConnection connection = Net.getConnection(player.clientid); - byte[] bytes = syncStream.toByteArray(); if(connection.lastSnapshot == null){ //no snapshot to diff, send it all Call.onSnapshot(connection.id, bytes, -1); }else{ - //send diff otherwise + //increment snapshot ID + connection.lastSnapshotID ++; + //send diff, otherwise byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder); Call.onSnapshot(connection.id, diff, connection.lastSnapshotID); @@ -240,7 +259,7 @@ public class NetServer extends Module{ @Remote(server = false) public static void connectConfirm(Player player){ player.add(); - Log.info("&y{0} has connected.", player.name); netCommon.sendMessage("[accent]" + player.name + " has connected."); + Log.info("&y{0} has connected.", player.name); } } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index b817029d3d..9286b911ce 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -41,6 +41,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { private static final float dashSpeed = 1.8f; private static final Vector2 movement = new Vector2(); + public static final int timerShootLeft = 0; + public static final int timerShootRight = 1; + public static final int timeSync = 2; + //region instance variables, constructor public float baseRotation; diff --git a/core/src/io/anuke/mindustry/net/NetConnection.java b/core/src/io/anuke/mindustry/net/NetConnection.java index c326e862af..6abdb6c1e5 100644 --- a/core/src/io/anuke/mindustry/net/NetConnection.java +++ b/core/src/io/anuke/mindustry/net/NetConnection.java @@ -7,9 +7,12 @@ public abstract class NetConnection { public final String address; /**ID of last snapshot this connection is guaranteed to have recieved.*/ - public int lastSnapshotID; - /**Byte array of last sent snapshot data that is confirmed to be recieved..*/ + public int lastSnapshotID = -1; + /**Byte array of last sent snapshot data that is confirmed to be recieved.*/ public byte[] lastSnapshot; + + /**ID of last sent snapshot.*/ + public int lastSentSnapshotID = -1; /**Byte array of last sent snapshot.*/ public byte[] lastSentSnapshot; diff --git a/core/src/io/anuke/mindustry/net/NetEvents.java b/core/src/io/anuke/mindustry/net/NetEvents.java index 9e33132648..2b338fc885 100644 --- a/core/src/io/anuke/mindustry/net/NetEvents.java +++ b/core/src/io/anuke/mindustry/net/NetEvents.java @@ -1,17 +1,5 @@ package io.anuke.mindustry.net; -import io.anuke.annotations.Annotations.Remote; -import io.anuke.mindustry.entities.Player; - public class NetEvents { - @Remote(one = true, all = false, unreliable = true) - public static void onSnapshot(byte[] snapshot, int snapshotID){ - - } - - @Remote(unreliable = true, server = false) - public static void onRecievedSnapshot(Player player, int snapshotID){ - Net.getConnection(player.clientid).lastSnapshotID = snapshotID; - } } diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index bef9a2c79a..35a4968049 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -100,11 +100,13 @@ public class Packets { public static class ClientSnapshotPacket implements Packet{ public Player player; + public int lastSnapshot; @Override public void write(ByteBuffer buffer) { ByteBufferOutput out = new ByteBufferOutput(buffer); + buffer.putInt(lastSnapshot); buffer.putInt(player.id); buffer.putLong(TimeUtils.millis()); player.write(out); @@ -114,6 +116,7 @@ public class Packets { public void read(ByteBuffer buffer) { ByteBufferInput in = new ByteBufferInput(buffer); + lastSnapshot = buffer.getInt(); int id = buffer.getInt(); long time = buffer.getLong(); player = Vars.playerGroup.getByID(id); diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java index a576fb45cf..0e0a6fd9c5 100644 --- a/core/src/io/anuke/mindustry/type/Weapon.java +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -50,8 +50,8 @@ public class Weapon extends Upgrade { } public void update(Player p, boolean left, float pointerX, float pointerY){ - int t = left ? 1 : 2; - int t2 = !left ? 1 : 2; + int t = left ? Player.timerShootLeft : Player.timerShootRight; + int t2 = !left ? Player.timerShootRight : Player.timerShootLeft; if(p.inventory.hasAmmo() && p.timer.get(t, reload)){ if(roundrobin){ p.timer.reset(t2, reload/2f); @@ -70,7 +70,7 @@ public class Weapon extends Upgrade { } public float getRecoil(Player player, boolean left){ - return 1f-Mathf.clamp(player.timer.getTime(left ? 1 : 2)/reload); + return 1f-Mathf.clamp(player.timer.getTime(left ? Player.timerShootLeft : Player.timerShootRight)/reload); } public float getReload(){