Fixed jittery snapshots / Fixed misaligned unit bullets

This commit is contained in:
Anuken
2018-08-26 17:38:22 -04:00
parent 864c4f6bc3
commit 06ad35d934
13 changed files with 132 additions and 70 deletions

View File

@ -47,7 +47,7 @@ public class Mechs implements ContentList{
boostSpeed = 0.85f;
weapon = Weapons.blaster;
maxSpeed = 4f;
altChargeAlpha = 0.04f;
altChargeAlpha = 0.02f;
trailColorTo = Color.valueOf("ffd37f");
armor = 20f;
}
@ -64,8 +64,8 @@ public class Mechs implements ContentList{
drone.leader = player;
drone.set(player.x, player.y);
drone.add();
Effects.effect(UnitFx.unitLand, player);
}
Effects.effect(UnitFx.unitLand, player);
player.altHeat = 0f;
}
}

View File

@ -30,7 +30,7 @@ public class UnitTypes implements ContentList{
speed = 0.5f;
maxVelocity = 1.6f;
range = 40f;
health = 20;
health = 30;
weapon = Weapons.droneBlaster;
trailColor = Color.valueOf("ffd37f");
}

View File

@ -3,12 +3,16 @@ package io.anuke.mindustry.core;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Base64Coder;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.IntMap.Entry;
import com.badlogic.gdx.utils.IntSet;
import io.anuke.annotations.Annotations.PacketPriority;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.annotations.Annotations.Variant;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.entities.traits.TypeTrait;
import io.anuke.mindustry.gen.Call;
@ -18,6 +22,7 @@ import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.net.TraceInfo;
import io.anuke.mindustry.world.modules.InventoryModule;
import io.anuke.ucore.core.Settings;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities;
@ -41,6 +46,7 @@ import static io.anuke.mindustry.Vars.*;
public class NetClient extends Module{
private final static float dataTimeout = 60 * 18;
private final static float playerSyncTime = 2;
private final static IntArray removals = new IntArray();
private Timer timer = new Timer(5);
/**Whether the client is currently connecting.*/
@ -54,8 +60,8 @@ public class NetClient extends Module{
/**Last snapshot ID recieved.*/
private int lastSnapshotBaseID = -1;
/**Last snapshot recieved.*/
private byte[] lastSnapshotBase;
private IntMap<byte[]> recievedSnapshots = new IntMap<>();
/**Current snapshot that is being built from chinks.*/
private byte[] currentSnapshot;
/**Array of recieved chunk statuses.*/
@ -191,12 +197,12 @@ public class NetClient extends Module{
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){
if(NetServer.showSnapshotSize)
if(NetServer.debugSnapshots)
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);
//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");
if(base != -1 && (snapshotID < netClient.lastSnapshotBaseID || !netClient.recievedSnapshots.containsKey(base))){
if(NetServer.debugSnapshots) Log.info("//SKIP SNAPSHOT");
return;
}
@ -235,7 +241,7 @@ public class NetClient extends Module{
snapshot = chunk;
}
if(NetServer.showSnapshotSize)
if(NetServer.debugSnapshots)
Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length);
byte[] result;
@ -243,20 +249,21 @@ public class NetClient extends Module{
if(base == -1){ //fresh snapshot
result = snapshot;
length = snapshot.length;
netClient.lastSnapshotBase = Arrays.copyOf(snapshot, snapshot.length);
netClient.recievedSnapshots.put(snapshotID, Arrays.copyOf(snapshot, snapshot.length));
}else{ //otherwise, last snapshot must not be null, decode it
if(NetServer.showSnapshotSize)
Log.info("Base size: {0} Patch size: {1}", netClient.lastSnapshotBase.length, snapshot.length);
netClient.decoder.init(netClient.lastSnapshotBase, snapshot);
byte[] baseBytes = netClient.recievedSnapshots.get(base);
if(NetServer.debugSnapshots)
Log.info("Base size: {0} Patch size: {1}", baseBytes.length, snapshot.length);
netClient.decoder.init(baseBytes, snapshot);
result = netClient.decoder.decode();
length = netClient.decoder.getDecodedLength();
//set last snapshot to a copy to prevent issues
netClient.lastSnapshotBase = Arrays.copyOf(result, length);
netClient.recievedSnapshots.put(snapshotID, Arrays.copyOf(result, length));
}
netClient.lastSnapshotBaseID = snapshotID;
//set stream bytes to begin snapshot reaeding
//set stream bytes to begin snapshot reading
netClient.byteStream.setBytes(result, 0, length);
//get data input for reading from the stream
@ -266,6 +273,16 @@ public class NetClient extends Module{
//confirm that snapshot has been recieved
netClient.lastSnapshotBaseID = snapshotID;
removals.clear();
for(Entry<byte[]> entry : netClient.recievedSnapshots.entries()){
if(entry.key < base){
removals.add(entry.key);
}
}
for(int i = 0; i < removals.size; i++){
netClient.recievedSnapshots.remove(removals.get(i));
}
}catch(Exception e){
throw new RuntimeException(e);
}
@ -280,7 +297,12 @@ public class NetClient extends Module{
byte cores = input.readByte();
for(int i = 0; i < cores; i++){
int pos = input.readInt();
world.tile(pos).entity.items.read(input);
TileEntity entity = world.tile(pos).entity;
if(entity != null){
entity.items.read(input);
}else{
new InventoryModule().read(input);
}
}
long timestamp = input.readLong();
@ -364,7 +386,7 @@ public class NetClient extends Module{
connecting = true;
quiet = false;
lastSent = 0;
lastSnapshotBase = null;
recievedSnapshots.clear();
currentSnapshot = null;
currentSnapshotID = -1;
lastSnapshotBaseID = -1;

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Colors;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.annotations.Annotations.Loc;
@ -45,11 +46,15 @@ import static io.anuke.mindustry.Vars.*;
public class NetServer extends Module{
public final static int maxSnapshotSize = 2047;
public final static boolean showSnapshotSize = false;
public final static boolean debugSnapshots = true;
public final static float maxSnapshotDelay = 200;
public final static float snapshotDropchance = 0.01f;
private final static byte[] reusableSnapArray = new byte[maxSnapshotSize];
private final static float serverSyncTime = 4, kickDuration = 30 * 1000;
private final static Vector2 vector = new Vector2();
private final static IntArray removals = new IntArray();
/**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/
private final static float correctDist = 16f;
@ -215,6 +220,7 @@ public class NetServer extends Module{
player.setMineTile(packet.mining);
player.isBoosting = packet.boosting;
player.isShooting = packet.shooting;
player.isAlt = packet.alting;
player.getPlaceQueue().clear();
for(BuildRequest req : packet.requests){
//auto-skip done requests
@ -231,7 +237,7 @@ public class NetServer extends Module{
float prevx = player.x, prevy = player.y;
player.set(player.getInterpolator().target.x, player.getInterpolator().target.y);
if(!player.mech.flying){
if(!player.mech.flying && player.boostHeat < 0.01f){
player.move(vector.x, vector.y);
}else{
player.x += vector.x;
@ -257,9 +263,18 @@ public class NetServer extends Module{
player.getVelocity().set(packet.xv, packet.yv); //only for visual calculation purposes, doesn't actually update the player
//when the client confirms recieveing a snapshot, update base and clear map
if(packet.lastSnapshot > connection.currentBaseID){
connection.currentBaseID = packet.lastSnapshot;
connection.currentBaseSnapshot = connection.lastSentRawSnapshot;
if(packet.lastSnapshot > connection.lastRecievedSnapshotID){
connection.lastRecievedSnapshotID = packet.lastSnapshot;
removals.clear();
for(IntMap.Entry entry : connection.sent){
if(entry.key < packet.lastSnapshot){
removals.add(entry.key);
}
}
for(int i = 0; i < removals.size; i++){
connection.sent.remove(removals.get(i));
}
}
connection.lastRecievedClientSnapshot = packet.snapid;
@ -285,7 +300,7 @@ public class NetServer extends Module{
/** Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/
private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){
if(bytes.length < maxSnapshotSize){
Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length, base);
scheduleSnapshot(() -> Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length, base));
}else{
int remaining = bytes.length;
int offset = 0;
@ -294,13 +309,15 @@ public class NetServer extends Module{
int used = Math.min(remaining, maxSnapshotSize);
byte[] toSend;
//re-use sent byte arrays when possible
if(used == maxSnapshotSize){
if(used == maxSnapshotSize && !debugSnapshots){
toSend = reusableSnapArray;
System.arraycopy(bytes, offset, toSend, 0, Math.min(offset + maxSnapshotSize, bytes.length) - offset);
}else{
toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length));
}
Call.onSnapshot(userid, toSend, snapshotID, (short) chunkid, bytes.length, base);
short fchunk = (short)chunkid;
scheduleSnapshot(() -> Call.onSnapshot(userid, toSend, snapshotID, fchunk, bytes.length, base));
remaining -= used;
offset += used;
@ -309,6 +326,16 @@ public class NetServer extends Module{
}
}
private static void scheduleSnapshot(Runnable r){
if(debugSnapshots){
if(!Mathf.chance(snapshotDropchance)){
Timers.run(maxSnapshotDelay / 1000f * 60f, r);
}
}else{
r.run();
}
}
public void sendWorldData(Player player, int clientID){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
DeflaterOutputStream def = new DeflaterOutputStream(stream);
@ -490,10 +517,6 @@ public class NetServer extends Module{
}
}
String getUUID(int connectionID){
return connections.get(connectionID).uuid;
}
String fixName(String name){
if(name.equals("[") || name.equals("]")){
return "";
@ -559,12 +582,12 @@ public class NetServer extends Module{
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(connection.currentBaseID < connection.lastSentSnapshotID){
if(showSnapshotSize)
/*if(connection.currentBaseID < connection.lastSentSnapshotID){
if(debugSnapshots)
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;
}
}*/
//reset stream to begin writing
syncStream.reset();
@ -573,29 +596,28 @@ public class NetServer extends Module{
byte[] bytes = syncStream.toByteArray();
if(connection.currentBaseID == -1){
//assign to last sent snapshot so that there is only ever one unique snapshot with ID 0
if(connection.lastSentSnapshot != null){
int snapid = connection.lastSentSnapshotID ++;
if(connection.lastRecievedSnapshotID == -1){
/*if(connection.lastSentSnapshot != null){
bytes = connection.lastSentSnapshot;
}else{
connection.lastSentRawSnapshot = bytes;
connection.lastSentSnapshot = bytes;
}
}*/
if(showSnapshotSize) Log.info("Sent raw snapshot: {0} bytes.", bytes.length);
if(debugSnapshots) Log.info("Sent raw snapshot: {0} bytes.", bytes.length);
///Nothing to diff off of in this case, send the whole thing
sendSplitSnapshot(connection.id, bytes, 0, -1);
sendSplitSnapshot(connection.id, bytes, snapid, -1);
connection.sent.put(snapid, bytes);
}else{
connection.lastSentRawSnapshot = bytes;
//send diff, otherwise
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.currentBaseSnapshot, bytes), encoder);
if(showSnapshotSize)
Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3} base length = {4}", bytes.length, diff.length, connection.currentBaseID, connection.currentBaseID + 1, connection.currentBaseSnapshot.length);
sendSplitSnapshot(connection.id, diff, connection.currentBaseID + 1, connection.currentBaseID);
connection.lastSentSnapshot = diff;
connection.lastSentSnapshotID = connection.currentBaseID + 1;
connection.lastSentBase = connection.currentBaseID;
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.sent.get(connection.lastRecievedSnapshotID), bytes), encoder);
if(debugSnapshots)
Log.info("Shrank snapshot: {0} -> {1}, Base {2}", bytes.length, diff.length, connection.lastRecievedSnapshotID);
sendSplitSnapshot(connection.id, diff, snapid, connection.lastRecievedSnapshotID);
connection.sent.put(snapid, bytes);
}
}

View File

@ -460,6 +460,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
altHeat = Mathf.lerpDelta(altHeat, isAlt ? 1f : 0f, mech.altChargeAlpha);
boostHeat = Mathf.lerpDelta(boostHeat, (tile != null && tile.solid()) || (isBoosting && ((!movement.isZero() && moved) || !isLocal)) ? 1f : 0f, 0.08f);
mech.updateAlt(this); //updated regardless
if(!isLocal){
interpolate();
@ -480,8 +481,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
control.database().unlockContent(mech);
}
mech.updateAlt(this);
if(mobile){
updateFlying();
}else{

View File

@ -23,7 +23,7 @@ public class AlphaDrone extends FlyingUnit {
public final UnitState attack = new UnitState() {
@Override
public void update() {
if(leader == null || leader.isDead()){
if(leader == null || leader.isDead() || !leader.isAdded()){
damage(99999f);
return;
}
@ -67,12 +67,19 @@ public class AlphaDrone extends FlyingUnit {
}
@Override
public void writeSave(DataOutput stream) throws IOException {
super.writeSave(stream);
public void write(DataOutput stream) throws IOException {
super.write(stream);
stream.writeInt(leader == null ? -1 : leader.id);
}
@Override
public void readSave(DataInput stream) throws IOException {
public void read(DataInput stream, long time) throws IOException {
super.read(stream, time);
leader = Vars.playerGroup.getByID(stream.readInt());
}
@Override
public void readSave(DataInput stream) throws IOException{
super.readSave(stream);
if(!Net.active()){

View File

@ -19,7 +19,7 @@ public class Interpolator{
public void read(float cx, float cy, float x, float y, long sent, float... target1ds){
if(lastUpdated != 0) updateSpacing = TimeUtils.timeSinceMillis(lastUpdated);
lastUpdated = sent;
lastUpdated = TimeUtils.millis();
targets = target1ds;
last.set(cx, cy);

View File

@ -1,5 +1,6 @@
package io.anuke.mindustry.net;
import com.badlogic.gdx.utils.IntMap;
import io.anuke.mindustry.net.Net.SendMode;
public abstract class NetConnection{
@ -10,24 +11,22 @@ public abstract class NetConnection{
* The current base snapshot that the client is absolutely confirmed to have recieved.
* All sent snapshots should be taking the diff from this base snapshot, if it isn't null.
*/
public byte[] currentBaseSnapshot;
//public byte[] currentBaseSnapshot;
/**
* ID of the current base snapshot.
*/
public int currentBaseID = -1;
// public int currentBaseID = -1;
public int lastSentBase = -1;
public byte[] lastSentSnapshot;
public byte[] lastSentRawSnapshot;
//public int lastSentBase = -1;
// public byte[] lastSentSnapshot;
//public byte[] lastSentRawSnapshot;
public int lastRecievedSnapshotID = -1;
public int lastSentSnapshotID = -1;
public IntMap<byte[]> sent = new IntMap<>();
/**
* ID of last recieved client snapshot.
*/
/**ID of last recieved client snapshot.*/
public int lastRecievedClientSnapshot = -1;
/**
* Timestamp of last recieved snapshot.
*/
/**Timestamp of last recieved snapshot.*/
public long lastRecievedClientTime;
public boolean hasConnected = false;

View File

@ -155,7 +155,7 @@ public class Packets{
//player snapshot data
public float x, y, pointerX, pointerY, rotation, baseRotation, xv, yv;
public Tile mining;
public boolean boosting, shooting;
public boolean boosting, shooting, alting;
public Array<BuildRequest> requests = new Array<>();
@Override
@ -172,6 +172,7 @@ public class Packets{
buffer.putFloat(player.pointerY);
buffer.put(player.isBoosting ? (byte) 1 : 0);
buffer.put(player.isShooting ? (byte) 1 : 0);
buffer.put(player.isAlt ? (byte) 1 : 0);
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));
@ -204,6 +205,7 @@ public class Packets{
pointerY = buffer.getFloat();
boosting = buffer.get() == 1;
shooting = buffer.get() == 1;
alting = buffer.get() == 1;
xv = buffer.get() / Unit.velocityPercision;
yv = buffer.get() / Unit.velocityPercision;
rotation = buffer.getShort() / 2f;

View File

@ -81,7 +81,10 @@ public class Weapon implements Content{
shootDirect(shooter, x, y, rotation, left);
}
public static void shootDirect(ShooterTrait shooter, float x, float y, float rotation, boolean left){
public static void shootDirect(ShooterTrait shooter, float offsetX, float offsetY, float rotation, boolean left){
float x = shooter.getX() + offsetX;
float y = shooter.getY() + offsetY;
Weapon weapon = shooter.getWeapon();
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> weapon.bullet(shooter, x, y, f + Mathf.range(weapon.inaccuracy)));
@ -149,7 +152,7 @@ public class Weapon implements Content{
float ang = tr.angle();
tr.trns(ang - 90, width * Mathf.sign(left), length);
shoot(shooter, shooter.getX() + tr.x, shooter.getY() + tr.y, Angles.angle(shooter.getX() + tr.x, shooter.getY() + tr.y, cx, cy), left);
shoot(shooter, tr.x, tr.y, Angles.angle(shooter.getX() + tr.x, shooter.getY() + tr.y, cx, cy), left);
}
}

View File

@ -352,7 +352,9 @@ public class JoinDialog extends FloatingDialog{
this.ip = ip;
this.port = Vars.port;
}
}else{
this.ip = ip;
this.port = Vars.port;
}
}

View File

@ -179,6 +179,10 @@ public class CoreBlock extends StorageBlock{
}
if(entity.currentUnit != null){
if(!entity.currentUnit.isDead()){
entity.currentUnit = null;
return;
}
entity.heat = Mathf.lerpDelta(entity.heat, 1f, 0.1f);
entity.time += Timers.delta();
entity.progress += 1f / (entity.currentUnit instanceof Player ? state.mode.respawnTime : droneRespawnDuration) * Timers.delta();

View File

@ -163,6 +163,7 @@ public class ServerControl extends Module{
handler.register("stop", "Stop hosting the server.", arg -> {
Net.closeServer();
Timers.clear();
state.set(State.menu);
netServer.reset();
Log.info("Stopped server.");
@ -889,6 +890,7 @@ public class ServerControl extends Module{
private void play(boolean wait, Runnable run){
inExtraRound = true;
Runnable r = () -> {
Array<Player> players = new Array<>();
for(Player p : playerGroup.all()){
players.add(p);
@ -906,7 +908,7 @@ public class ServerControl extends Module{
};
if(wait){
Timers.runTask(60f * roundExtraTime, r);
Timers.run(60f * roundExtraTime, r);
}else{
r.run();
}