Extensive netcode changes, bugfixes
This commit is contained in:
@@ -27,7 +27,7 @@ allprojects {
|
|||||||
gdxVersion = '1.9.8'
|
gdxVersion = '1.9.8'
|
||||||
roboVMVersion = '2.3.0'
|
roboVMVersion = '2.3.0'
|
||||||
aiVersion = '1.8.1'
|
aiVersion = '1.8.1'
|
||||||
uCoreVersion = '5d685fc8b1'
|
uCoreVersion = '2a575ccc77'
|
||||||
|
|
||||||
getVersionString = {
|
getVersionString = {
|
||||||
String buildVersion = getBuildVersion()
|
String buildVersion = getBuildVersion()
|
||||||
|
|||||||
@@ -47,8 +47,11 @@ public class NetClient extends Module {
|
|||||||
private float timeoutTime = 0f;
|
private float timeoutTime = 0f;
|
||||||
/**Last sent client snapshot ID.*/
|
/**Last sent client snapshot ID.*/
|
||||||
private int lastSent;
|
private int lastSent;
|
||||||
|
|
||||||
|
/**Last snapshot ID recieved.*/
|
||||||
|
private int lastSnapshotBaseID = -1;
|
||||||
/**Last snapshot recieved.*/
|
/**Last snapshot recieved.*/
|
||||||
private byte[] lastSnapshot;
|
private byte[] lastSnapshotBase;
|
||||||
/**Current snapshot that is being built from chinks.*/
|
/**Current snapshot that is being built from chinks.*/
|
||||||
private byte[] currentSnapshot;
|
private byte[] currentSnapshot;
|
||||||
/**Array of recieved chunk statuses.*/
|
/**Array of recieved chunk statuses.*/
|
||||||
@@ -57,8 +60,7 @@ public class NetClient extends Module {
|
|||||||
private int recievedChunkCounter;
|
private int recievedChunkCounter;
|
||||||
/**ID of snapshot that is currently being constructed.*/
|
/**ID of snapshot that is currently being constructed.*/
|
||||||
private int currentSnapshotID = -1;
|
private int currentSnapshotID = -1;
|
||||||
/**Last snapshot ID recieved.*/
|
|
||||||
private int lastSnapshotID = -1;
|
|
||||||
/**Decoder for uncompressing snapshots.*/
|
/**Decoder for uncompressing snapshots.*/
|
||||||
private DEZDecoder decoder = new DEZDecoder();
|
private DEZDecoder decoder = new DEZDecoder();
|
||||||
/**List of entities that were removed, and need not be added while syncing.*/
|
/**List of entities that were removed, and need not be added while syncing.*/
|
||||||
@@ -80,15 +82,22 @@ public class NetClient extends Module {
|
|||||||
connecting = true;
|
connecting = true;
|
||||||
quiet = false;
|
quiet = false;
|
||||||
lastSent = 0;
|
lastSent = 0;
|
||||||
lastSnapshot = null;
|
lastSnapshotBase = null;
|
||||||
currentSnapshot = null;
|
currentSnapshot = null;
|
||||||
currentSnapshotID = -1;
|
currentSnapshotID = -1;
|
||||||
lastSnapshotID = -1;
|
lastSnapshotBaseID = -1;
|
||||||
|
|
||||||
ui.chatfrag.clearMessages();
|
ui.chatfrag.clearMessages();
|
||||||
ui.loadfrag.hide();
|
ui.loadfrag.hide();
|
||||||
ui.loadfrag.show("$text.connecting.data");
|
ui.loadfrag.show("$text.connecting.data");
|
||||||
|
|
||||||
|
ui.loadfrag.setButton(() -> {
|
||||||
|
ui.loadfrag.hide();
|
||||||
|
connecting = false;
|
||||||
|
quiet = true;
|
||||||
|
Net.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
Entities.clear();
|
Entities.clear();
|
||||||
|
|
||||||
ConnectPacket c = new ConnectPacket();
|
ConnectPacket c = new ConnectPacket();
|
||||||
@@ -191,7 +200,7 @@ public class NetClient extends Module {
|
|||||||
if(timer.get(0, playerSyncTime)){
|
if(timer.get(0, playerSyncTime)){
|
||||||
|
|
||||||
ClientSnapshotPacket packet = Pools.obtain(ClientSnapshotPacket.class);
|
ClientSnapshotPacket packet = Pools.obtain(ClientSnapshotPacket.class);
|
||||||
packet.lastSnapshot = lastSnapshotID;
|
packet.lastSnapshot = lastSnapshotBaseID;
|
||||||
packet.snapid = lastSent++;
|
packet.snapid = lastSent++;
|
||||||
Net.send(packet, SendMode.udp);
|
Net.send(packet, SendMode.udp);
|
||||||
}
|
}
|
||||||
@@ -239,9 +248,12 @@ public class NetClient extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Remote(variants = Variant.one, unreliable = true)
|
@Remote(variants = Variant.one, unreliable = true)
|
||||||
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, short totalLength){
|
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, short totalLength, int base){
|
||||||
//skip snapshot IDs that have already been recieved
|
if(NetServer.showSnapshotSize) Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID);
|
||||||
if(snapshotID == netClient.lastSnapshotID){
|
|
||||||
|
//skip snapshot IDs that have already been recieved OR snapshots that are too far in front
|
||||||
|
if(snapshotID < netClient.lastSnapshotBaseID || base != netClient.lastSnapshotBaseID){
|
||||||
|
if(NetServer.showSnapshotSize) Log.info("//SKIP SNAPSHOT");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,21 +292,24 @@ public class NetClient extends Module {
|
|||||||
snapshot = chunk;
|
snapshot = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(NetServer.showSnapshotSize) Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length);
|
||||||
|
|
||||||
byte[] result;
|
byte[] result;
|
||||||
int length;
|
int length;
|
||||||
if (snapshotID == 0) { //fresh snapshot
|
if (base == -1) { //fresh snapshot
|
||||||
result = snapshot;
|
result = snapshot;
|
||||||
length = snapshot.length;
|
length = snapshot.length;
|
||||||
netClient.lastSnapshot = Arrays.copyOf(snapshot, snapshot.length);
|
netClient.lastSnapshotBase = Arrays.copyOf(snapshot, snapshot.length);
|
||||||
} else { //otherwise, last snapshot must not be null, decode it
|
} else { //otherwise, last snapshot must not be null, decode it
|
||||||
netClient.decoder.init(netClient.lastSnapshot, snapshot);
|
if(NetServer.showSnapshotSize) Log.info("Base size: {0} Path size: {1}", netClient.lastSnapshotBase.length, snapshot.length);
|
||||||
|
netClient.decoder.init(netClient.lastSnapshotBase, snapshot);
|
||||||
result = netClient.decoder.decode();
|
result = netClient.decoder.decode();
|
||||||
length = netClient.decoder.getDecodedLength();
|
length = netClient.decoder.getDecodedLength();
|
||||||
//set last snapshot to a copy to prevent issues
|
//set last snapshot to a copy to prevent issues
|
||||||
netClient.lastSnapshot = Arrays.copyOf(result, length);
|
netClient.lastSnapshotBase = Arrays.copyOf(result, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
netClient.lastSnapshotID = snapshotID;
|
netClient.lastSnapshotBaseID = snapshotID;
|
||||||
|
|
||||||
//set stream bytes to begin snapshot reaeding
|
//set stream bytes to begin snapshot reaeding
|
||||||
netClient.byteStream.setBytes(result, 0, length);
|
netClient.byteStream.setBytes(result, 0, length);
|
||||||
@@ -355,7 +370,7 @@ public class NetClient extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//confirm that snapshot has been recieved
|
//confirm that snapshot has been recieved
|
||||||
netClient.lastSnapshotID = snapshotID;
|
netClient.lastSnapshotBaseID = snapshotID;
|
||||||
|
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ import static io.anuke.mindustry.Vars.*;
|
|||||||
|
|
||||||
public class NetServer extends Module{
|
public class NetServer extends Module{
|
||||||
public final static int maxSnapshotSize = 2047;
|
public final static int maxSnapshotSize = 2047;
|
||||||
|
public final static boolean showSnapshotSize = false;
|
||||||
|
|
||||||
private final static byte[] reusableSnapArray = new byte[maxSnapshotSize];
|
private final static byte[] reusableSnapArray = new byte[maxSnapshotSize];
|
||||||
private final static boolean showSnapshotSize = false;
|
|
||||||
private final static float serverSyncTime = 4, kickDuration = 30 * 1000;
|
private final static float serverSyncTime = 4, kickDuration = 30 * 1000;
|
||||||
private final static Vector2 vector = new Vector2();
|
private final static Vector2 vector = new Vector2();
|
||||||
/**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/
|
/**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/
|
||||||
@@ -167,15 +167,15 @@ public class NetServer extends Module{
|
|||||||
Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> {
|
Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> {
|
||||||
Player player = connections.get(id);
|
Player player = connections.get(id);
|
||||||
NetConnection connection = Net.getConnection(id);
|
NetConnection connection = Net.getConnection(id);
|
||||||
if(player == null || connection == null || packet.snapid < connection.lastRecievedSnapshot) return;
|
if(player == null || connection == null || packet.snapid < connection.lastRecievedClientSnapshot) return;
|
||||||
|
|
||||||
boolean verifyPosition = !player.isDead() && !debug && headless;
|
boolean verifyPosition = !player.isDead() && !debug && headless;
|
||||||
|
|
||||||
if(connection.lastRecievedTime == 0) connection.lastRecievedTime = TimeUtils.millis() - 16;
|
if(connection.lastRecievedClientTime == 0) connection.lastRecievedClientTime = TimeUtils.millis() - 16;
|
||||||
|
|
||||||
long elapsed = TimeUtils.timeSinceMillis(connection.lastRecievedTime);
|
long elapsed = TimeUtils.timeSinceMillis(connection.lastRecievedClientTime);
|
||||||
|
|
||||||
float maxSpeed = packet.boosting && !player.mech.flying ? player.mech.boostSpeed*3f : player.mech.speed*3f;
|
float maxSpeed = (packet.boosting && !player.mech.flying ? player.mech.boostSpeed : player.mech.speed)*2.5f;
|
||||||
|
|
||||||
//extra 1.1x multiplicaton is added just in case
|
//extra 1.1x multiplicaton is added just in case
|
||||||
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
|
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
|
||||||
@@ -184,6 +184,7 @@ public class NetServer extends Module{
|
|||||||
player.pointerY = packet.pointerY;
|
player.pointerY = packet.pointerY;
|
||||||
player.setMineTile(packet.mining);
|
player.setMineTile(packet.mining);
|
||||||
player.isBoosting = packet.boosting;
|
player.isBoosting = packet.boosting;
|
||||||
|
player.isShooting = packet.shooting;
|
||||||
|
|
||||||
vector.set(packet.x - player.getInterpolator().target.x, packet.y - player.getInterpolator().target.y);
|
vector.set(packet.x - player.getInterpolator().target.x, packet.y - player.getInterpolator().target.y);
|
||||||
|
|
||||||
@@ -210,12 +211,21 @@ public class NetServer extends Module{
|
|||||||
player.getInterpolator().read(player.x, player.y, newx, newy, packet.timeSent, packet.rotation, packet.baseRotation);
|
player.getInterpolator().read(player.x, player.y, newx, newy, packet.timeSent, packet.rotation, packet.baseRotation);
|
||||||
player.getVelocity().set(packet.xv, packet.yv); //only for visual calculation purposes, doesn't actually update the player
|
player.getVelocity().set(packet.xv, packet.yv); //only for visual calculation purposes, doesn't actually update the player
|
||||||
|
|
||||||
connection.lastSnapshotID = packet.lastSnapshot;
|
//when the client confirms recieveing a snapshot, update base and clear map
|
||||||
connection.lastRecievedSnapshot = packet.snapid;
|
if(packet.lastSnapshot > connection.currentBaseID){
|
||||||
connection.lastRecievedTime = TimeUtils.millis();
|
connection.currentBaseID = packet.lastSnapshot;
|
||||||
|
connection.currentBaseSnapshot = connection.lastSentRawSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.lastRecievedClientSnapshot = packet.snapid;
|
||||||
|
connection.lastRecievedClientTime = TimeUtils.millis();
|
||||||
});
|
});
|
||||||
|
|
||||||
Net.handleServer(InvokePacket.class, (id, packet) -> RemoteReadServer.readPacket(packet.writeBuffer, packet.type, connections.get(id)));
|
Net.handleServer(InvokePacket.class, (id, packet) -> {
|
||||||
|
Player player = connections.get(id);
|
||||||
|
if(player == null) return;
|
||||||
|
RemoteReadServer.readPacket(packet.writeBuffer, packet.type, player);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(){
|
public void update(){
|
||||||
@@ -314,7 +324,7 @@ public class NetServer extends Module{
|
|||||||
for (Player player : connections.values()) {
|
for (Player player : connections.values()) {
|
||||||
NetConnection connection = Net.getConnection(player.clientid);
|
NetConnection connection = Net.getConnection(player.clientid);
|
||||||
|
|
||||||
if(connection == null){
|
if(connection == null || !connection.isConnected()){
|
||||||
//player disconnected, ignore them
|
//player disconnected, ignore them
|
||||||
onDisconnect(player);
|
onDisconnect(player);
|
||||||
return;
|
return;
|
||||||
@@ -323,18 +333,16 @@ public class NetServer extends Module{
|
|||||||
if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue;
|
if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue;
|
||||||
|
|
||||||
//if the player hasn't acknowledged that it has recieved the packet, send the same thing again
|
//if the player hasn't acknowledged that it has recieved the packet, send the same thing again
|
||||||
if(connection.lastSentSnapshotID > connection.lastSnapshotID){
|
if(connection.currentBaseID < connection.lastSentSnapshotID){
|
||||||
sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID);
|
if(showSnapshotSize) Log.info("Re-sending snapshot: {0} bytes, ID {1} base {2} baselength {3}", connection.lastSentSnapshot.length, connection.lastSentSnapshotID, connection.lastSentBase, connection.currentBaseSnapshot.length);
|
||||||
|
sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID, connection.lastSentBase);
|
||||||
return;
|
return;
|
||||||
}else{
|
|
||||||
//set up last confirmed snapshot to the last one that was sent, otherwise
|
|
||||||
connection.lastSnapshot = connection.lastSentSnapshot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//reset stream to begin writing
|
//reset stream to begin writing
|
||||||
syncStream.reset();
|
syncStream.reset();
|
||||||
|
|
||||||
//write wave data
|
//write wave datas
|
||||||
dataStream.writeFloat(state.wavetime);
|
dataStream.writeFloat(state.wavetime);
|
||||||
dataStream.writeInt(state.wave);
|
dataStream.writeInt(state.wave);
|
||||||
|
|
||||||
@@ -397,21 +405,22 @@ public class NetServer extends Module{
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = syncStream.toByteArray();
|
byte[] bytes = syncStream.toByteArray();
|
||||||
connection.lastSentSnapshot = bytes;
|
|
||||||
if(connection.lastSnapshotID == -1){
|
connection.lastSentRawSnapshot = bytes;
|
||||||
|
|
||||||
|
if(connection.currentBaseID == -1){
|
||||||
if(showSnapshotSize) Log.info("Sent raw snapshot: {0} bytes.", bytes.length);
|
if(showSnapshotSize) Log.info("Sent raw snapshot: {0} bytes.", bytes.length);
|
||||||
//no snapshot to diff, send it all
|
///Nothing to diff off of in this case, send the whole thing, but increment the counter
|
||||||
//Call.onSnapshot(connection.id, bytes, 0, 0);
|
connection.lastSentSnapshot = bytes;
|
||||||
sendSplitSnapshot(connection.id, bytes, 0);
|
sendSplitSnapshot(connection.id, bytes, 0, -1);
|
||||||
connection.lastSnapshotID = 0;
|
|
||||||
}else{
|
}else{
|
||||||
//send diff, otherwise
|
//send diff, otherwise
|
||||||
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder);
|
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.currentBaseSnapshot, bytes), encoder);
|
||||||
if(showSnapshotSize) Log.info("Shrank snapshot: {0} -> {1}", bytes.length, diff.length);
|
if(showSnapshotSize) Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3}", bytes.length, diff.length, connection.currentBaseID, connection.lastSentSnapshotID);
|
||||||
//Call.onSnapshot(connection.id, diff, connection.lastSnapshotID + 1, 0);
|
sendSplitSnapshot(connection.id, diff, connection.lastSentSnapshotID + 1, connection.currentBaseID);
|
||||||
sendSplitSnapshot(connection.id, diff, connection.lastSnapshotID + 1);
|
connection.lastSentSnapshot = diff;
|
||||||
//increment snapshot ID
|
connection.lastSentSnapshotID = connection.currentBaseID + 1;
|
||||||
connection.lastSentSnapshotID ++;
|
connection.lastSentBase = connection.currentBaseID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,9 +430,10 @@ public class NetServer extends Module{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/
|
/**Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/
|
||||||
private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID){
|
private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){
|
||||||
if(bytes.length < maxSnapshotSize){
|
if(bytes.length < maxSnapshotSize){
|
||||||
Call.onSnapshot(userid, bytes, snapshotID, (short)0, (short)bytes.length);
|
if(showSnapshotSize) Log.info("Raw send() snapshot call: {0} bytes, sID {1}", bytes.length, snapshotID);
|
||||||
|
Call.onSnapshot(userid, bytes, snapshotID, (short)0, (short)bytes.length, base);
|
||||||
}else{
|
}else{
|
||||||
int remaining = bytes.length;
|
int remaining = bytes.length;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
@@ -438,7 +448,7 @@ public class NetServer extends Module{
|
|||||||
}else {
|
}else {
|
||||||
toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length));
|
toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length));
|
||||||
}
|
}
|
||||||
Call.onSnapshot(userid, toSend, snapshotID, (short)chunkid, (short)bytes.length);
|
Call.onSnapshot(userid, toSend, snapshotID, (short)chunkid, (short)bytes.length, base);
|
||||||
|
|
||||||
remaining -= used;
|
remaining -= used;
|
||||||
offset += used;
|
offset += used;
|
||||||
|
|||||||
@@ -590,7 +590,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
|
|||||||
|
|
||||||
//autofire: mobile only!
|
//autofire: mobile only!
|
||||||
if(mobile) {
|
if(mobile) {
|
||||||
boolean lastShooting = isShooting;
|
|
||||||
|
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
isShooting = false;
|
isShooting = false;
|
||||||
@@ -609,10 +608,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
|
|||||||
isShooting = true;
|
isShooting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update status of shooting to server
|
|
||||||
if(lastShooting != isShooting){
|
|
||||||
CallEntity.setShooting(isShooting);
|
|
||||||
}
|
|
||||||
}else if(isShooting()){
|
}else if(isShooting()){
|
||||||
Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(),
|
Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(),
|
||||||
Vars.control.input(playerIndex).getMouseY());
|
Vars.control.input(playerIndex).getMouseY());
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
|||||||
import io.anuke.mindustry.content.blocks.Blocks;
|
import io.anuke.mindustry.content.blocks.Blocks;
|
||||||
import io.anuke.mindustry.core.GameState.State;
|
import io.anuke.mindustry.core.GameState.State;
|
||||||
import io.anuke.mindustry.entities.Player;
|
import io.anuke.mindustry.entities.Player;
|
||||||
import io.anuke.mindustry.gen.CallEntity;
|
|
||||||
import io.anuke.mindustry.graphics.Palette;
|
import io.anuke.mindustry.graphics.Palette;
|
||||||
import io.anuke.mindustry.input.PlaceUtils.NormalizeDrawResult;
|
import io.anuke.mindustry.input.PlaceUtils.NormalizeDrawResult;
|
||||||
import io.anuke.mindustry.input.PlaceUtils.NormalizeResult;
|
import io.anuke.mindustry.input.PlaceUtils.NormalizeResult;
|
||||||
@@ -140,7 +139,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(player.isShooting && !canShoot()){
|
if(player.isShooting && !canShoot()){
|
||||||
CallEntity.setShooting(false);
|
player.isShooting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isPlacing()){
|
if(isPlacing()){
|
||||||
@@ -207,7 +206,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
//only begin shooting if there's no cursor event
|
//only begin shooting if there's no cursor event
|
||||||
if(!tileTapped(cursor) && !tryTapPlayer(worldx, worldy) && player.getPlaceQueue().size == 0 && !droppingItem &&
|
if(!tileTapped(cursor) && !tryTapPlayer(worldx, worldy) && player.getPlaceQueue().size == 0 && !droppingItem &&
|
||||||
!tryBeginMine(cursor) && player.getMineTile() == null){
|
!tryBeginMine(cursor) && player.getMineTile() == null){
|
||||||
CallEntity.setShooting(true);
|
player.isShooting = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else if(button == Buttons.RIGHT){ //right = begin breaking
|
}else if(button == Buttons.RIGHT){ //right = begin breaking
|
||||||
@@ -229,7 +228,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
@Override
|
@Override
|
||||||
public boolean touchUp (int screenX, int screenY, int pointer, int button) {
|
public boolean touchUp (int screenX, int screenY, int pointer, int button) {
|
||||||
if(button == Buttons.LEFT){
|
if(button == Buttons.LEFT){
|
||||||
CallEntity.setShooting(false);
|
player.isShooting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return false;
|
if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return false;
|
||||||
|
|||||||
@@ -265,11 +265,6 @@ public abstract class InputHandler extends InputAdapter{
|
|||||||
player.addBuildRequest(new BuildRequest(tile.x, tile.y));
|
player.addBuildRequest(new BuildRequest(tile.x, tile.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Remote(targets = Loc.client, called = Loc.both, in = In.entities)
|
|
||||||
public static void setShooting(Player player, boolean on){
|
|
||||||
player.isShooting = on;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Remote(targets = Loc.both, called = Loc.server, in = In.entities)
|
@Remote(targets = Loc.both, called = Loc.server, in = In.entities)
|
||||||
public static void dropItem(Player player, float angle){
|
public static void dropItem(Player player, float angle){
|
||||||
if(Net.server() && !player.inventory.hasItem()){
|
if(Net.server() && !player.inventory.hasItem()){
|
||||||
@@ -332,6 +327,7 @@ public abstract class InputHandler extends InputAdapter{
|
|||||||
|
|
||||||
@Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks)
|
@Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks)
|
||||||
public static void onTileTapped(Player player, Tile tile){
|
public static void onTileTapped(Player player, Tile tile){
|
||||||
|
if(tile == null || player == null) return;
|
||||||
tile.block().tapped(tile, player);
|
tile.block().tapped(tile, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,21 @@ public abstract class NetConnection {
|
|||||||
public final int id;
|
public final int id;
|
||||||
public final String address;
|
public final String address;
|
||||||
|
|
||||||
/**ID of last snapshot this connection is guaranteed to have recieved.*/
|
/**The current base snapshot that the client is absolutely confirmed to have recieved.
|
||||||
public int lastSnapshotID = -1;
|
* All sent snapshots should be taking the diff from this base snapshot, if it isn't null.*/
|
||||||
/**Byte array of last sent snapshot data that is confirmed to be recieved.*/
|
public byte[] currentBaseSnapshot;
|
||||||
public byte[] lastSnapshot;
|
/**ID of the current base snapshot.*/
|
||||||
|
public int currentBaseID = -1;
|
||||||
|
|
||||||
/**ID of last sent snapshot.*/
|
public int lastSentBase = -1;
|
||||||
public int lastSentSnapshotID = -1;
|
|
||||||
/**Byte array of last sent snapshot.*/
|
|
||||||
public byte[] lastSentSnapshot;
|
public byte[] lastSentSnapshot;
|
||||||
|
public byte[] lastSentRawSnapshot;
|
||||||
|
public int lastSentSnapshotID = -1;
|
||||||
|
|
||||||
/**ID of last recieved client snapshot.*/
|
/**ID of last recieved client snapshot.*/
|
||||||
public int lastRecievedSnapshot = -1;
|
public int lastRecievedClientSnapshot = -1;
|
||||||
/**Timestamp of last recieved snapshot.*/
|
/**Timestamp of last recieved snapshot.*/
|
||||||
public long lastRecievedTime;
|
public long lastRecievedClientTime;
|
||||||
|
|
||||||
public boolean hasConnected = false;
|
public boolean hasConnected = false;
|
||||||
|
|
||||||
@@ -28,6 +29,10 @@ public abstract class NetConnection {
|
|||||||
this.address = address;
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isConnected(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void send(Object object, SendMode mode);
|
public abstract void send(Object object, SendMode mode);
|
||||||
public abstract void close();
|
public abstract void close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,29 +59,39 @@ public class NetworkIO {
|
|||||||
stream.writeShort(world.width());
|
stream.writeShort(world.width());
|
||||||
stream.writeShort(world.height());
|
stream.writeShort(world.height());
|
||||||
|
|
||||||
for(int x = 0; x < world.width(); x ++){
|
for (int i = 0; i < world.width() * world.height(); i++) {
|
||||||
for(int y = 0; y < world.height(); y ++){
|
Tile tile = world.tile(i);
|
||||||
Tile tile = world.tile(x, y);
|
|
||||||
|
|
||||||
stream.writeByte(tile.floor().id); //floor ID
|
stream.writeByte(tile.getFloorID());
|
||||||
stream.writeByte(tile.block().id); //block ID
|
stream.writeByte(tile.getWallID());
|
||||||
stream.writeByte(tile.elevation);
|
stream.writeByte(tile.elevation);
|
||||||
|
|
||||||
if(tile.block() instanceof BlockPart){
|
if(tile.block() instanceof BlockPart){
|
||||||
stream.writeByte(tile.link);
|
stream.writeByte(tile.link);
|
||||||
}
|
}else if(tile.entity != null){
|
||||||
|
stream.writeByte(Bits.packByte(tile.getTeamID(), tile.getRotation())); //team + rotation
|
||||||
|
stream.writeShort((short)tile.entity.health); //health
|
||||||
|
|
||||||
if(tile.entity != null){
|
if(tile.entity.items != null) tile.entity.items.write(stream);
|
||||||
stream.writeByte(Bits.packByte((byte)tile.getTeam().ordinal(), tile.getRotation()));
|
if(tile.entity.power != null) tile.entity.power.write(stream);
|
||||||
stream.writeShort((short)tile.entity.health); //health
|
if(tile.entity.liquids != null) tile.entity.liquids.write(stream);
|
||||||
|
|
||||||
if(tile.entity.items != null) tile.entity.items.write(stream);
|
tile.entity.write(stream);
|
||||||
if(tile.entity.power != null) tile.entity.power.write(stream);
|
}else if(tile.getWallID() == 0){
|
||||||
if(tile.entity.liquids != null) tile.entity.liquids.write(stream);
|
int consecutives = 0;
|
||||||
|
|
||||||
tile.entity.write(stream);
|
for (int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++) {
|
||||||
|
Tile nextTile = world.tile(j);
|
||||||
|
|
||||||
|
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getWallID() != 0 || nextTile.elevation != tile.elevation){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
consecutives ++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream.writeByte(consecutives);
|
||||||
|
i += consecutives;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,38 +168,47 @@ public class NetworkIO {
|
|||||||
|
|
||||||
Tile[][] tiles = world.createTiles(width, height);
|
Tile[][] tiles = world.createTiles(width, height);
|
||||||
|
|
||||||
for(int x = 0; x < width; x ++){
|
for (int i = 0; i < width * height; i++) {
|
||||||
for(int y = 0; y < height; y ++){
|
int x = i % width, y = i /width;
|
||||||
byte floorid = stream.readByte();
|
byte floorid = stream.readByte();
|
||||||
byte blockid = stream.readByte();
|
byte wallid = stream.readByte();
|
||||||
byte elevation = stream.readByte();
|
byte elevation = stream.readByte();
|
||||||
|
|
||||||
Tile tile = new Tile(x, y, floorid, blockid);
|
Tile tile = new Tile(x, y, floorid, wallid);
|
||||||
|
tile.elevation = elevation;
|
||||||
|
|
||||||
tile.elevation = elevation;
|
if (wallid == Blocks.blockpart.id) {
|
||||||
|
tile.link = stream.readByte();
|
||||||
|
}else if (tile.entity != null) {
|
||||||
|
byte tr = stream.readByte();
|
||||||
|
short health = stream.readShort();
|
||||||
|
|
||||||
if(tile.block() == Blocks.blockpart){
|
byte team = Bits.getLeftByte(tr);
|
||||||
tile.link = stream.readByte();
|
byte rotation = Bits.getRightByte(tr);
|
||||||
|
|
||||||
|
tile.setTeam(Team.all[team]);
|
||||||
|
tile.entity.health = health;
|
||||||
|
tile.setRotation(rotation);
|
||||||
|
|
||||||
|
if (tile.entity.items != null) tile.entity.items.read(stream);
|
||||||
|
if (tile.entity.power != null) tile.entity.power.read(stream);
|
||||||
|
if (tile.entity.liquids != null) tile.entity.liquids.read(stream);
|
||||||
|
|
||||||
|
tile.entity.read(stream);
|
||||||
|
}else if(wallid == 0){
|
||||||
|
int consecutives = stream.readUnsignedByte();
|
||||||
|
|
||||||
|
for (int j = i + 1; j < i + 1 + consecutives; j++) {
|
||||||
|
int newx = j % width, newy = j / width;
|
||||||
|
Tile newTile = new Tile(newx, newy, floorid, wallid);
|
||||||
|
newTile.elevation = elevation;
|
||||||
|
tiles[newx][newy] = newTile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(tile.entity != null) {
|
i += consecutives;
|
||||||
byte tr = stream.readByte();
|
|
||||||
short health = stream.readShort();
|
|
||||||
|
|
||||||
tile.setTeam(Team.all[Bits.getLeftByte(tr)]);
|
|
||||||
tile.setRotation(Bits.getRightByte(tr));
|
|
||||||
|
|
||||||
tile.entity.health = health;
|
|
||||||
|
|
||||||
if (tile.entity.items != null) tile.entity.items.read(stream);
|
|
||||||
if (tile.entity.power != null) tile.entity.power.read(stream);
|
|
||||||
if (tile.entity.liquids != null) tile.entity.liquids.read(stream);
|
|
||||||
|
|
||||||
tile.entity.read(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles[x][y] = tile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tiles[x][y] = tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.reset();
|
player.reset();
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class Packets {
|
|||||||
//player snapshot data
|
//player snapshot data
|
||||||
public float x, y, pointerX, pointerY, rotation, baseRotation, xv, yv;
|
public float x, y, pointerX, pointerY, rotation, baseRotation, xv, yv;
|
||||||
public Tile mining;
|
public Tile mining;
|
||||||
public boolean boosting;
|
public boolean boosting, shooting;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(ByteBuffer buffer) {
|
public void write(ByteBuffer buffer) {
|
||||||
@@ -123,6 +123,7 @@ public class Packets {
|
|||||||
buffer.putFloat(player.pointerX);
|
buffer.putFloat(player.pointerX);
|
||||||
buffer.putFloat(player.pointerY);
|
buffer.putFloat(player.pointerY);
|
||||||
buffer.put(player.isBoosting ? (byte)1 : 0);
|
buffer.put(player.isBoosting ? (byte)1 : 0);
|
||||||
|
buffer.put(player.isShooting ? (byte)1 : 0);
|
||||||
|
|
||||||
buffer.put((byte)(Mathf.clamp(player.getVelocity().x, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision));
|
buffer.put((byte)(Mathf.clamp(player.getVelocity().x, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision));
|
||||||
buffer.put((byte)(Mathf.clamp(player.getVelocity().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision));
|
buffer.put((byte)(Mathf.clamp(player.getVelocity().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision));
|
||||||
@@ -144,6 +145,7 @@ public class Packets {
|
|||||||
pointerX = buffer.getFloat();
|
pointerX = buffer.getFloat();
|
||||||
pointerY = buffer.getFloat();
|
pointerY = buffer.getFloat();
|
||||||
boosting = buffer.get() == 1;
|
boosting = buffer.get() == 1;
|
||||||
|
shooting = buffer.get() == 1;
|
||||||
xv = buffer.get() / Unit.velocityPercision;
|
xv = buffer.get() / Unit.velocityPercision;
|
||||||
yv = buffer.get() / Unit.velocityPercision;
|
yv = buffer.get() / Unit.velocityPercision;
|
||||||
rotation = buffer.getShort()/2f;
|
rotation = buffer.getShort()/2f;
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ import io.anuke.ucore.util.Bundles;
|
|||||||
import io.anuke.ucore.util.Log;
|
import io.anuke.ucore.util.Log;
|
||||||
import io.anuke.ucore.util.Strings;
|
import io.anuke.ucore.util.Strings;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.maxNameLength;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
import static io.anuke.mindustry.Vars.players;
|
|
||||||
import static io.anuke.mindustry.Vars.ui;
|
|
||||||
|
|
||||||
public class JoinDialog extends FloatingDialog {
|
public class JoinDialog extends FloatingDialog {
|
||||||
Array<Server> servers = new Array<>();
|
Array<Server> servers = new Array<>();
|
||||||
@@ -275,6 +273,11 @@ public class JoinDialog extends FloatingDialog {
|
|||||||
void connect(String ip, int port){
|
void connect(String ip, int port){
|
||||||
ui.loadfrag.show("$text.connecting");
|
ui.loadfrag.show("$text.connecting");
|
||||||
|
|
||||||
|
ui.loadfrag.setButton(() -> {
|
||||||
|
ui.loadfrag.hide();
|
||||||
|
netClient.disconnectQuietly();
|
||||||
|
});
|
||||||
|
|
||||||
Timers.runTask(2f, () -> {
|
Timers.runTask(2f, () -> {
|
||||||
try{
|
try{
|
||||||
Vars.netClient.beginConnecting();
|
Vars.netClient.beginConnecting();
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public class SettingsMenuDialog extends SettingsDialog{
|
|||||||
game.sliderPref("saveinterval", 90, 10, 5*120, i -> Bundles.format("setting.seconds", i));
|
game.sliderPref("saveinterval", 90, 10, 5*120, i -> Bundles.format("setting.seconds", i));
|
||||||
|
|
||||||
if(!gwt){
|
if(!gwt){
|
||||||
graphics.checkPref("multithread", false, threads::setEnabled);
|
graphics.checkPref("multithread", true, threads::setEnabled);
|
||||||
|
|
||||||
if(Settings.getBool("multithread")){
|
if(Settings.getBool("multithread")){
|
||||||
threads.setEnabled(true);
|
threads.setEnabled(true);
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package io.anuke.mindustry.ui.fragments;
|
package io.anuke.mindustry.ui.fragments;
|
||||||
|
|
||||||
import io.anuke.mindustry.graphics.Palette;
|
import io.anuke.mindustry.graphics.Palette;
|
||||||
|
import io.anuke.ucore.function.Listenable;
|
||||||
import io.anuke.ucore.scene.Group;
|
import io.anuke.ucore.scene.Group;
|
||||||
import io.anuke.ucore.scene.builders.label;
|
import io.anuke.ucore.scene.builders.label;
|
||||||
import io.anuke.ucore.scene.builders.table;
|
import io.anuke.ucore.scene.builders.table;
|
||||||
import io.anuke.ucore.scene.event.Touchable;
|
import io.anuke.ucore.scene.event.Touchable;
|
||||||
import io.anuke.ucore.scene.ui.Label;
|
import io.anuke.ucore.scene.ui.Label;
|
||||||
|
import io.anuke.ucore.scene.ui.TextButton;
|
||||||
import io.anuke.ucore.scene.ui.layout.Table;
|
import io.anuke.ucore.scene.ui.layout.Table;
|
||||||
|
|
||||||
public class LoadingFragment implements Fragment {
|
public class LoadingFragment implements Fragment {
|
||||||
private Table table;
|
private Table table;
|
||||||
|
private TextButton button;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void build(Group parent) {
|
public void build(Group parent) {
|
||||||
@@ -25,11 +28,22 @@ public class LoadingFragment implements Fragment {
|
|||||||
row();
|
row();
|
||||||
get().addImage("white").growX()
|
get().addImage("white").growX()
|
||||||
.height(3f).pad(4f).growX().get().setColor(Palette.accent);
|
.height(3f).pad(4f).growX().get().setColor(Palette.accent);
|
||||||
|
|
||||||
|
row();
|
||||||
|
|
||||||
|
button = get().addButton("$text.cancel", () -> {}).pad(20).size(250f, 70f).get();
|
||||||
|
button.setVisible(false);
|
||||||
}}.end().get();
|
}}.end().get();
|
||||||
|
|
||||||
table.setVisible(false);
|
table.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setButton(Listenable listener){
|
||||||
|
button.setVisible(true);
|
||||||
|
button.getListeners().removeIndex(button.getListeners().size - 1);
|
||||||
|
button.clicked(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void show(){
|
public void show(){
|
||||||
show("$text.loading");
|
show("$text.loading");
|
||||||
}
|
}
|
||||||
@@ -42,5 +56,6 @@ public class LoadingFragment implements Fragment {
|
|||||||
|
|
||||||
public void hide(){
|
public void hide(){
|
||||||
table.setVisible(false);
|
table.setVisible(false);
|
||||||
|
button.setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ public class Build {
|
|||||||
/**Returns block type that was broken, or null if unsuccesful.*/
|
/**Returns block type that was broken, or null if unsuccesful.*/
|
||||||
@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks)
|
@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks)
|
||||||
public static void breakBlock(Player player, Team team, int x, int y){
|
public static void breakBlock(Player player, Team team, int x, int y){
|
||||||
if(Net.server() && !validBreak(team, x, y)){
|
if(Net.server()){
|
||||||
return;
|
if(!validBreak(team, x, y)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
team = player.getTeam();
|
||||||
//throw new ValidateException(player, "An invalid block has been broken.");
|
//throw new ValidateException(player, "An invalid block has been broken.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +47,7 @@ public class Build {
|
|||||||
Block previous = tile.block();
|
Block previous = tile.block();
|
||||||
|
|
||||||
//remote players only
|
//remote players only
|
||||||
if(!player.isLocal){
|
if(player != null && !player.isLocal){
|
||||||
player.getPlaceQueue().clear();
|
player.getPlaceQueue().clear();
|
||||||
player.getPlaceQueue().addFirst(new BuildRequest(x, y));
|
player.getPlaceQueue().addFirst(new BuildRequest(x, y));
|
||||||
}
|
}
|
||||||
@@ -88,8 +92,12 @@ public class Build {
|
|||||||
/**Places a BuildBlock at this location. Call validPlace first.*/
|
/**Places a BuildBlock at this location. Call validPlace first.*/
|
||||||
@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks)
|
@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks)
|
||||||
public static void placeBlock(Player player, Team team, int x, int y, Recipe recipe, int rotation){
|
public static void placeBlock(Player player, Team team, int x, int y, Recipe recipe, int rotation){
|
||||||
if(Net.server() && !validPlace(team, x, y, recipe.result, rotation)){
|
if(Net.server()){
|
||||||
return;
|
if(!validPlace(team, x, y, recipe.result, rotation)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
team = player.getTeam();
|
||||||
//throw new ValidateException(player, "An invalid block has been placed.");
|
//throw new ValidateException(player, "An invalid block has been placed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +151,9 @@ public class Build {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
threads.runDelay(() -> Events.fire(BlockBuildEvent.class, team, tile));
|
Team fteam = team;
|
||||||
|
|
||||||
|
threads.runDelay(() -> Events.fire(BlockBuildEvent.class, fteam, tile));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Returns whether a tile can be placed at this location by this team.*/
|
/**Returns whether a tile can be placed at this location by this team.*/
|
||||||
|
|||||||
@@ -140,9 +140,12 @@ public class BreakBlock extends Block {
|
|||||||
|
|
||||||
@Remote(called = Loc.server, in = In.blocks)
|
@Remote(called = Loc.server, in = In.blocks)
|
||||||
public static void onBreakFinish(Tile tile){
|
public static void onBreakFinish(Tile tile){
|
||||||
BreakEntity entity = tile.entity();
|
|
||||||
|
|
||||||
Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), entity.previous.size);
|
if(tile.entity instanceof BreakEntity){
|
||||||
|
BreakEntity entity = tile.entity();
|
||||||
|
Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), entity.previous.size);
|
||||||
|
}
|
||||||
|
|
||||||
world.removeBlock(tile);
|
world.removeBlock(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package io.anuke.mindustry.desktop;
|
package io.anuke.mindustry.desktop;
|
||||||
|
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Net;
|
||||||
|
import io.anuke.ucore.core.Settings;
|
||||||
import io.anuke.ucore.util.Strings;
|
import io.anuke.ucore.util.Strings;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@@ -14,8 +16,6 @@ public class CrashHandler {
|
|||||||
//TODO send full error report to server via HTTP
|
//TODO send full error report to server via HTTP
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//attempt to close connections, if applicable
|
//attempt to close connections, if applicable
|
||||||
try{
|
try{
|
||||||
Net.dispose();
|
Net.dispose();
|
||||||
@@ -26,8 +26,20 @@ public class CrashHandler {
|
|||||||
//don't create crash logs for me (anuke), as it's expected
|
//don't create crash logs for me (anuke), as it's expected
|
||||||
if(System.getProperty("user.name").equals("anuke")) return;
|
if(System.getProperty("user.name").equals("anuke")) return;
|
||||||
|
|
||||||
|
String header = "";
|
||||||
|
|
||||||
|
try{
|
||||||
|
header += "--GAME INFO-- \n";
|
||||||
|
header += "Multithreading: " + Settings.getBool("multithread")+ "\n";
|
||||||
|
header += "Net Active: " + Net.active()+ "\n";
|
||||||
|
header += "Net Server: " + Net.server()+ "\n";
|
||||||
|
header += "OS: " + System.getProperty("os.name")+ "\n----\n";
|
||||||
|
}catch (Throwable e4){
|
||||||
|
e4.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
//parse exception
|
//parse exception
|
||||||
String result = Strings.parseException(e, true);
|
String result = header + Strings.parseFullException(e);
|
||||||
boolean failed = false;
|
boolean failed = false;
|
||||||
|
|
||||||
String filename = "";
|
String filename = "";
|
||||||
@@ -42,9 +54,9 @@ public class CrashHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
//JOptionPane.showMessageDialog(null, "An error has occured: \n" + result + "\n\n" +
|
javax.swing.JOptionPane.showMessageDialog(null, "An error has occured: \n" + result + "\n\n" +
|
||||||
// (!failed ? "A crash report has been written to " + new File(filename).getAbsolutePath() + ".\nPlease send this file to the developer!"
|
(!failed ? "A crash report has been written to " + new File(filename).getAbsolutePath() + ".\nPlease send this file to the developer!"
|
||||||
// : "Failed to generate crash report.\nPlease send an image of this crash log to the developer!"));
|
: "Failed to generate crash report.\nPlease send an image of this crash log to the developer!"));
|
||||||
}catch (Throwable i){
|
}catch (Throwable i){
|
||||||
i.printStackTrace();
|
i.printStackTrace();
|
||||||
//what now?
|
//what now?
|
||||||
|
|||||||
89
kryonet/src/io/anuke/kryonet/CustomListeners.java
Normal file
89
kryonet/src/io/anuke/kryonet/CustomListeners.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package io.anuke.kryonet;
|
||||||
|
|
||||||
|
import com.esotericsoftware.kryonet.Connection;
|
||||||
|
import com.esotericsoftware.kryonet.Listener;
|
||||||
|
import com.esotericsoftware.kryonet.Listener.QueuedListener;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class CustomListeners {
|
||||||
|
|
||||||
|
static public class LagListener extends QueuedListener {
|
||||||
|
protected final ScheduledExecutorService threadPool;
|
||||||
|
private final int lagMillisMin, lagMillisMax;
|
||||||
|
final LinkedList<Runnable> runnables = new LinkedList();
|
||||||
|
|
||||||
|
public LagListener (int lagMillisMin, int lagMillisMax, Listener listener) {
|
||||||
|
super(listener);
|
||||||
|
this.lagMillisMin = lagMillisMin;
|
||||||
|
this.lagMillisMax = lagMillisMax;
|
||||||
|
threadPool = Executors.newScheduledThreadPool(1, r -> {
|
||||||
|
Thread t = Executors.defaultThreadFactory().newThread(r);
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int calculateLag() {
|
||||||
|
return lagMillisMin + (int)(Math.random() * (lagMillisMax - lagMillisMin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queue (Runnable runnable) {
|
||||||
|
|
||||||
|
synchronized (runnables) {
|
||||||
|
runnables.addFirst(runnable);
|
||||||
|
}
|
||||||
|
threadPool.schedule(() -> {
|
||||||
|
Runnable runnable1;
|
||||||
|
synchronized (runnables) {
|
||||||
|
runnable1 = runnables.removeLast();
|
||||||
|
}
|
||||||
|
runnable1.run();
|
||||||
|
}, calculateLag(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delays, reorders and does not make guarantees to the delivery of incoming objects
|
||||||
|
* to the wrapped listener (in order to simulate lag, jitter, package loss and
|
||||||
|
* package duplication).
|
||||||
|
* Notification events are likely processed on a separate thread after a delay.
|
||||||
|
* Note that only the delivery of incoming objects is modified. To modify the delivery
|
||||||
|
* of outgoing objects, use a UnreliableListener at the other end of the connection.
|
||||||
|
*/
|
||||||
|
static public class UnreliableListener extends LagListener {
|
||||||
|
private final float lossPercentage;
|
||||||
|
private final float duplicationPercentage;
|
||||||
|
private final CustomListeners.LagListener tcpListener;
|
||||||
|
|
||||||
|
public UnreliableListener (int lagMillisMin, int lagMillisMax, float lossPercentage,
|
||||||
|
float duplicationPercentage, Listener listener) {
|
||||||
|
super(lagMillisMin, lagMillisMax, listener);
|
||||||
|
this.tcpListener = new CustomListeners.LagListener(lagMillisMin, lagMillisMax, listener);
|
||||||
|
this.lossPercentage = lossPercentage;
|
||||||
|
this.duplicationPercentage = duplicationPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void received(Connection connection, Object object) {
|
||||||
|
if(KryoCore.lastUDP) {
|
||||||
|
super.received(connection, object);
|
||||||
|
}else{
|
||||||
|
tcpListener.received(connection, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queue (Runnable runnable) {
|
||||||
|
do {
|
||||||
|
if (Math.random() >= lossPercentage) {
|
||||||
|
threadPool.schedule(runnable, calculateLag(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
} while (Math.random() < duplicationPercentage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ import com.badlogic.gdx.utils.Array;
|
|||||||
import com.badlogic.gdx.utils.ObjectMap;
|
import com.badlogic.gdx.utils.ObjectMap;
|
||||||
import com.badlogic.gdx.utils.ObjectSet;
|
import com.badlogic.gdx.utils.ObjectSet;
|
||||||
import com.esotericsoftware.kryonet.*;
|
import com.esotericsoftware.kryonet.*;
|
||||||
import com.esotericsoftware.kryonet.Listener.LagListener;
|
|
||||||
import com.esotericsoftware.minlog.Log;
|
import com.esotericsoftware.minlog.Log;
|
||||||
|
import io.anuke.kryonet.CustomListeners.UnreliableListener;
|
||||||
import io.anuke.mindustry.net.Host;
|
import io.anuke.mindustry.net.Host;
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Net;
|
||||||
import io.anuke.mindustry.net.Net.ClientProvider;
|
import io.anuke.mindustry.net.Net.ClientProvider;
|
||||||
@@ -25,9 +25,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.channels.ClosedSelectorException;
|
import java.nio.channels.ClosedSelectorException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.netClient;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
import static io.anuke.mindustry.Vars.port;
|
|
||||||
import static io.anuke.mindustry.Vars.threads;
|
|
||||||
|
|
||||||
public class KryoClient implements ClientProvider{
|
public class KryoClient implements ClientProvider{
|
||||||
Client client;
|
Client client;
|
||||||
@@ -35,6 +33,8 @@ public class KryoClient implements ClientProvider{
|
|||||||
ClientDiscoveryHandler handler;
|
ClientDiscoveryHandler handler;
|
||||||
|
|
||||||
public KryoClient(){
|
public KryoClient(){
|
||||||
|
KryoCore.init();
|
||||||
|
|
||||||
handler = new ClientDiscoveryHandler() {
|
handler = new ClientDiscoveryHandler() {
|
||||||
@Override
|
@Override
|
||||||
public DatagramPacket onRequestNewDatagramPacket() {
|
public DatagramPacket onRequestNewDatagramPacket() {
|
||||||
@@ -80,7 +80,7 @@ public class KryoClient implements ClientProvider{
|
|||||||
public void received (Connection connection, Object object) {
|
public void received (Connection connection, Object object) {
|
||||||
if(object instanceof FrameworkMessage) return;
|
if(object instanceof FrameworkMessage) return;
|
||||||
|
|
||||||
Gdx.app.postRunnable(() -> {
|
threads.runDelay(() -> {
|
||||||
try{
|
try{
|
||||||
Net.handleClientReceived(object);
|
Net.handleClientReceived(object);
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
@@ -97,8 +97,8 @@ public class KryoClient implements ClientProvider{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(KryoRegistrator.fakeLag){
|
if(KryoCore.fakeLag){
|
||||||
client.addListener(new LagListener(KryoRegistrator.fakeLagMin, KryoRegistrator.fakeLagMax, listener));
|
client.addListener(new UnreliableListener(KryoCore.fakeLagMin, KryoCore.fakeLagMax, KryoCore.fakeLagDrop, KryoCore.fakeLagDuplicate, listener));
|
||||||
}else{
|
}else{
|
||||||
client.addListener(listener);
|
client.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|||||||
89
kryonet/src/io/anuke/kryonet/KryoCore.java
Normal file
89
kryonet/src/io/anuke/kryonet/KryoCore.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package io.anuke.kryonet;
|
||||||
|
|
||||||
|
import com.esotericsoftware.minlog.Log;
|
||||||
|
import com.esotericsoftware.minlog.Log.Logger;
|
||||||
|
import io.anuke.ucore.util.ColorCodes;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static io.anuke.mindustry.Vars.headless;
|
||||||
|
|
||||||
|
/**Utilities and configs for kryo module.*/
|
||||||
|
public class KryoCore {
|
||||||
|
public static boolean fakeLag = false;
|
||||||
|
public static final int fakeLagMax = 500;
|
||||||
|
public static final int fakeLagMin = 0;
|
||||||
|
public static final float fakeLagDrop = 0.1f;
|
||||||
|
public static final float fakeLagDuplicate = 0.1f;
|
||||||
|
|
||||||
|
public static boolean lastUDP;
|
||||||
|
|
||||||
|
private static ScheduledExecutorService threadPool;
|
||||||
|
|
||||||
|
public static void init(){
|
||||||
|
Log.set(fakeLag ? Log.LEVEL_DEBUG : Log.LEVEL_WARN);
|
||||||
|
|
||||||
|
Log.setLogger(new Logger(){
|
||||||
|
public void log (int level, String category, String message, Throwable ex) {
|
||||||
|
if(fakeLag){
|
||||||
|
if(message.contains("UDP")){
|
||||||
|
lastUDP = true;
|
||||||
|
}else if(message.contains("TCP")){
|
||||||
|
lastUDP = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(256);
|
||||||
|
|
||||||
|
if(headless)
|
||||||
|
builder.append(ColorCodes.BLUE);
|
||||||
|
|
||||||
|
builder.append("Net Error: ");
|
||||||
|
|
||||||
|
builder.append(message);
|
||||||
|
|
||||||
|
if (ex != null) {
|
||||||
|
StringWriter writer = new StringWriter(256);
|
||||||
|
ex.printStackTrace(new PrintWriter(writer));
|
||||||
|
builder.append('\n');
|
||||||
|
builder.append(writer.toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(headless)
|
||||||
|
builder.append(ColorCodes.RESET);
|
||||||
|
|
||||||
|
io.anuke.ucore.util.Log.info("&b" + builder.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateLag() {
|
||||||
|
return fakeLagMin + (int)(Math.random() * (fakeLagMax - fakeLagMin));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**Executes something in a potentially unreliable way. Used to simulate lag and packet errors with UDP.*/
|
||||||
|
public static void recieveUnreliable(Runnable run){
|
||||||
|
if(fakeLag && threadPool == null){
|
||||||
|
threadPool = Executors.newScheduledThreadPool(1, r -> {
|
||||||
|
Thread t = Executors.defaultThreadFactory().newThread(r);
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fakeLag){
|
||||||
|
do {
|
||||||
|
if (Math.random() >= fakeLagDrop) {
|
||||||
|
threadPool.schedule(run, calculateLag(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
} while (Math.random() < fakeLagDuplicate);
|
||||||
|
}else{
|
||||||
|
run.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package io.anuke.kryonet;
|
|
||||||
|
|
||||||
import com.esotericsoftware.minlog.Log;
|
|
||||||
import com.esotericsoftware.minlog.Log.Logger;
|
|
||||||
import io.anuke.ucore.util.ColorCodes;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.headless;
|
|
||||||
|
|
||||||
public class KryoRegistrator {
|
|
||||||
public static boolean fakeLag = false;
|
|
||||||
public static final int fakeLagMax = 1000;
|
|
||||||
public static final int fakeLagMin = 0;
|
|
||||||
|
|
||||||
static{
|
|
||||||
Log.set(Log.LEVEL_WARN);
|
|
||||||
|
|
||||||
Log.setLogger(new Logger(){
|
|
||||||
public void log (int level, String category, String message, Throwable ex) {
|
|
||||||
StringBuilder builder = new StringBuilder(256);
|
|
||||||
|
|
||||||
if(headless)
|
|
||||||
builder.append(ColorCodes.BLUE);
|
|
||||||
|
|
||||||
builder.append("Net Error: ");
|
|
||||||
|
|
||||||
builder.append(message);
|
|
||||||
|
|
||||||
if (ex != null) {
|
|
||||||
StringWriter writer = new StringWriter(256);
|
|
||||||
ex.printStackTrace(new PrintWriter(writer));
|
|
||||||
builder.append('\n');
|
|
||||||
builder.append(writer.toString().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(headless)
|
|
||||||
builder.append(ColorCodes.RESET);
|
|
||||||
|
|
||||||
io.anuke.ucore.util.Log.info("&b" + builder.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,9 +6,9 @@ import com.badlogic.gdx.utils.Base64Coder;
|
|||||||
import com.esotericsoftware.kryonet.Connection;
|
import com.esotericsoftware.kryonet.Connection;
|
||||||
import com.esotericsoftware.kryonet.FrameworkMessage;
|
import com.esotericsoftware.kryonet.FrameworkMessage;
|
||||||
import com.esotericsoftware.kryonet.Listener;
|
import com.esotericsoftware.kryonet.Listener;
|
||||||
import com.esotericsoftware.kryonet.Listener.LagListener;
|
|
||||||
import com.esotericsoftware.kryonet.Server;
|
import com.esotericsoftware.kryonet.Server;
|
||||||
import com.esotericsoftware.kryonet.util.InputStreamSender;
|
import com.esotericsoftware.kryonet.util.InputStreamSender;
|
||||||
|
import io.anuke.kryonet.CustomListeners.UnreliableListener;
|
||||||
import io.anuke.mindustry.Vars;
|
import io.anuke.mindustry.Vars;
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Net;
|
||||||
import io.anuke.mindustry.net.Net.SendMode;
|
import io.anuke.mindustry.net.Net.SendMode;
|
||||||
@@ -17,9 +17,9 @@ import io.anuke.mindustry.net.NetConnection;
|
|||||||
import io.anuke.mindustry.net.NetworkIO;
|
import io.anuke.mindustry.net.NetworkIO;
|
||||||
import io.anuke.mindustry.net.Packets.Connect;
|
import io.anuke.mindustry.net.Packets.Connect;
|
||||||
import io.anuke.mindustry.net.Packets.Disconnect;
|
import io.anuke.mindustry.net.Packets.Disconnect;
|
||||||
import io.anuke.mindustry.net.Streamable;
|
|
||||||
import io.anuke.mindustry.net.Packets.StreamBegin;
|
import io.anuke.mindustry.net.Packets.StreamBegin;
|
||||||
import io.anuke.mindustry.net.Packets.StreamChunk;
|
import io.anuke.mindustry.net.Packets.StreamChunk;
|
||||||
|
import io.anuke.mindustry.net.Streamable;
|
||||||
import io.anuke.ucore.UCore;
|
import io.anuke.ucore.UCore;
|
||||||
import io.anuke.ucore.core.Timers;
|
import io.anuke.ucore.core.Timers;
|
||||||
import io.anuke.ucore.util.Log;
|
import io.anuke.ucore.util.Log;
|
||||||
@@ -52,6 +52,8 @@ public class KryoServer implements ServerProvider {
|
|||||||
int lastconnection = 0;
|
int lastconnection = 0;
|
||||||
|
|
||||||
public KryoServer(){
|
public KryoServer(){
|
||||||
|
KryoCore.init();
|
||||||
|
|
||||||
server = new Server(4096*2, 4096, connection -> new ByteSerializer());
|
server = new Server(4096*2, 4096, connection -> new ByteSerializer());
|
||||||
server.setDiscoveryHandler((datagramChannel, fromAddress) -> {
|
server.setDiscoveryHandler((datagramChannel, fromAddress) -> {
|
||||||
ByteBuffer buffer = NetworkIO.writeServerData();
|
ByteBuffer buffer = NetworkIO.writeServerData();
|
||||||
@@ -110,8 +112,8 @@ public class KryoServer implements ServerProvider {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(KryoRegistrator.fakeLag){
|
if(KryoCore.fakeLag){
|
||||||
server.addListener(new LagListener(KryoRegistrator.fakeLagMin, KryoRegistrator.fakeLagMax, listener));
|
server.addListener(new UnreliableListener(KryoCore.fakeLagMin, KryoCore.fakeLagMax, KryoCore.fakeLagDrop, KryoCore.fakeLagDuplicate, listener));
|
||||||
}else{
|
}else{
|
||||||
server.addListener(listener);
|
server.addListener(listener);
|
||||||
}
|
}
|
||||||
@@ -323,6 +325,11 @@ public class KryoServer implements ServerProvider {
|
|||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected(){
|
||||||
|
return connection == null ? !socket.isClosed() : connection.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(Object object, SendMode mode){
|
public void send(Object object, SendMode mode){
|
||||||
if(socket != null){
|
if(socket != null){
|
||||||
|
|||||||
Reference in New Issue
Block a user