Extensive netcode changes, bugfixes

This commit is contained in:
Anuken
2018-06-29 14:13:31 -04:00
parent 3898602f0e
commit b4151a256b
20 changed files with 411 additions and 187 deletions

View File

@@ -47,8 +47,11 @@ public class NetClient extends Module {
private float timeoutTime = 0f;
/**Last sent client snapshot ID.*/
private int lastSent;
/**Last snapshot ID recieved.*/
private int lastSnapshotBaseID = -1;
/**Last snapshot recieved.*/
private byte[] lastSnapshot;
private byte[] lastSnapshotBase;
/**Current snapshot that is being built from chinks.*/
private byte[] currentSnapshot;
/**Array of recieved chunk statuses.*/
@@ -57,8 +60,7 @@ public class NetClient extends Module {
private int recievedChunkCounter;
/**ID of snapshot that is currently being constructed.*/
private int currentSnapshotID = -1;
/**Last snapshot ID recieved.*/
private int lastSnapshotID = -1;
/**Decoder for uncompressing snapshots.*/
private DEZDecoder decoder = new DEZDecoder();
/**List of entities that were removed, and need not be added while syncing.*/
@@ -80,15 +82,22 @@ public class NetClient extends Module {
connecting = true;
quiet = false;
lastSent = 0;
lastSnapshot = null;
lastSnapshotBase = null;
currentSnapshot = null;
currentSnapshotID = -1;
lastSnapshotID = -1;
lastSnapshotBaseID = -1;
ui.chatfrag.clearMessages();
ui.loadfrag.hide();
ui.loadfrag.show("$text.connecting.data");
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
connecting = false;
quiet = true;
Net.disconnect();
});
Entities.clear();
ConnectPacket c = new ConnectPacket();
@@ -191,7 +200,7 @@ public class NetClient extends Module {
if(timer.get(0, playerSyncTime)){
ClientSnapshotPacket packet = Pools.obtain(ClientSnapshotPacket.class);
packet.lastSnapshot = lastSnapshotID;
packet.lastSnapshot = lastSnapshotBaseID;
packet.snapid = lastSent++;
Net.send(packet, SendMode.udp);
}
@@ -239,9 +248,12 @@ public class NetClient extends Module {
}
@Remote(variants = Variant.one, unreliable = true)
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, short totalLength){
//skip snapshot IDs that have already been recieved
if(snapshotID == netClient.lastSnapshotID){
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, short totalLength, int base){
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);
//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;
}
@@ -280,21 +292,24 @@ public class NetClient extends Module {
snapshot = chunk;
}
if(NetServer.showSnapshotSize) Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length);
byte[] result;
int length;
if (snapshotID == 0) { //fresh snapshot
if (base == -1) { //fresh snapshot
result = snapshot;
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
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();
length = netClient.decoder.getDecodedLength();
//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
netClient.byteStream.setBytes(result, 0, length);
@@ -355,7 +370,7 @@ public class NetClient extends Module {
}
//confirm that snapshot has been recieved
netClient.lastSnapshotID = snapshotID;
netClient.lastSnapshotBaseID = snapshotID;
}catch (Exception e){
throw new RuntimeException(e);

View File

@@ -40,9 +40,9 @@ import static io.anuke.mindustry.Vars.*;
public class NetServer extends Module{
public final static int maxSnapshotSize = 2047;
public final static boolean showSnapshotSize = false;
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 Vector2 vector = new Vector2();
/**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) -> {
Player player = connections.get(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;
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
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
@@ -184,6 +184,7 @@ public class NetServer extends Module{
player.pointerY = packet.pointerY;
player.setMineTile(packet.mining);
player.isBoosting = packet.boosting;
player.isShooting = packet.shooting;
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.getVelocity().set(packet.xv, packet.yv); //only for visual calculation purposes, doesn't actually update the player
connection.lastSnapshotID = packet.lastSnapshot;
connection.lastRecievedSnapshot = packet.snapid;
connection.lastRecievedTime = TimeUtils.millis();
//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;
}
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(){
@@ -314,7 +324,7 @@ public class NetServer extends Module{
for (Player player : connections.values()) {
NetConnection connection = Net.getConnection(player.clientid);
if(connection == null){
if(connection == null || !connection.isConnected()){
//player disconnected, ignore them
onDisconnect(player);
return;
@@ -323,18 +333,16 @@ 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.lastSentSnapshotID > connection.lastSnapshotID){
sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID);
if(connection.currentBaseID < 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;
}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();
//write wave data
//write wave datas
dataStream.writeFloat(state.wavetime);
dataStream.writeInt(state.wave);
@@ -397,21 +405,22 @@ public class NetServer extends Module{
}
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);
//no snapshot to diff, send it all
//Call.onSnapshot(connection.id, bytes, 0, 0);
sendSplitSnapshot(connection.id, bytes, 0);
connection.lastSnapshotID = 0;
///Nothing to diff off of in this case, send the whole thing, but increment the counter
connection.lastSentSnapshot = bytes;
sendSplitSnapshot(connection.id, bytes, 0, -1);
}else{
//send diff, otherwise
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder);
if(showSnapshotSize) Log.info("Shrank snapshot: {0} -> {1}", bytes.length, diff.length);
//Call.onSnapshot(connection.id, diff, connection.lastSnapshotID + 1, 0);
sendSplitSnapshot(connection.id, diff, connection.lastSnapshotID + 1);
//increment snapshot ID
connection.lastSentSnapshotID ++;
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.currentBaseSnapshot, bytes), encoder);
if(showSnapshotSize) Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3}", bytes.length, diff.length, connection.currentBaseID, connection.lastSentSnapshotID);
sendSplitSnapshot(connection.id, diff, connection.lastSentSnapshotID + 1, connection.currentBaseID);
connection.lastSentSnapshot = diff;
connection.lastSentSnapshotID = connection.currentBaseID + 1;
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.*/
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){
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{
int remaining = bytes.length;
int offset = 0;
@@ -438,7 +448,7 @@ public class NetServer extends Module{
}else {
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;
offset += used;

View File

@@ -590,7 +590,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
//autofire: mobile only!
if(mobile) {
boolean lastShooting = isShooting;
if (target == null) {
isShooting = false;
@@ -609,10 +608,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
isShooting = true;
}
//update status of shooting to server
if(lastShooting != isShooting){
CallEntity.setShooting(isShooting);
}
}else if(isShooting()){
Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(),
Vars.control.input(playerIndex).getMouseY());

View File

@@ -6,7 +6,6 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion;
import io.anuke.mindustry.content.blocks.Blocks;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.gen.CallEntity;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.input.PlaceUtils.NormalizeDrawResult;
import io.anuke.mindustry.input.PlaceUtils.NormalizeResult;
@@ -140,7 +139,7 @@ public class DesktopInput extends InputHandler{
}
if(player.isShooting && !canShoot()){
CallEntity.setShooting(false);
player.isShooting = false;
}
if(isPlacing()){
@@ -207,7 +206,7 @@ public class DesktopInput extends InputHandler{
//only begin shooting if there's no cursor event
if(!tileTapped(cursor) && !tryTapPlayer(worldx, worldy) && player.getPlaceQueue().size == 0 && !droppingItem &&
!tryBeginMine(cursor) && player.getMineTile() == null){
CallEntity.setShooting(true);
player.isShooting = true;
}
}
}else if(button == Buttons.RIGHT){ //right = begin breaking
@@ -229,7 +228,7 @@ public class DesktopInput extends InputHandler{
@Override
public boolean touchUp (int screenX, int screenY, int pointer, int button) {
if(button == Buttons.LEFT){
CallEntity.setShooting(false);
player.isShooting = false;
}
if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return false;

View File

@@ -265,11 +265,6 @@ public abstract class InputHandler extends InputAdapter{
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)
public static void dropItem(Player player, float angle){
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)
public static void onTileTapped(Player player, Tile tile){
if(tile == null || player == null) return;
tile.block().tapped(tile, player);
}

View File

@@ -6,20 +6,21 @@ public abstract class NetConnection {
public final int id;
public final String address;
/**ID of last snapshot this connection is guaranteed to have recieved.*/
public int lastSnapshotID = -1;
/**Byte array of last sent snapshot data that is confirmed to be recieved.*/
public byte[] lastSnapshot;
/**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;
/**ID of the current base snapshot.*/
public int currentBaseID = -1;
/**ID of last sent snapshot.*/
public int lastSentSnapshotID = -1;
/**Byte array of last sent snapshot.*/
public int lastSentBase = -1;
public byte[] lastSentSnapshot;
public byte[] lastSentRawSnapshot;
public int lastSentSnapshotID = -1;
/**ID of last recieved client snapshot.*/
public int lastRecievedSnapshot = -1;
public int lastRecievedClientSnapshot = -1;
/**Timestamp of last recieved snapshot.*/
public long lastRecievedTime;
public long lastRecievedClientTime;
public boolean hasConnected = false;
@@ -28,6 +29,10 @@ public abstract class NetConnection {
this.address = address;
}
public boolean isConnected(){
return true;
}
public abstract void send(Object object, SendMode mode);
public abstract void close();
}

View File

@@ -59,29 +59,39 @@ public class NetworkIO {
stream.writeShort(world.width());
stream.writeShort(world.height());
for(int x = 0; x < world.width(); x ++){
for(int y = 0; y < world.height(); y ++){
Tile tile = world.tile(x, y);
for (int i = 0; i < world.width() * world.height(); i++) {
Tile tile = world.tile(i);
stream.writeByte(tile.floor().id); //floor ID
stream.writeByte(tile.block().id); //block ID
stream.writeByte(tile.elevation);
stream.writeByte(tile.getFloorID());
stream.writeByte(tile.getWallID());
stream.writeByte(tile.elevation);
if(tile.block() instanceof BlockPart){
stream.writeByte(tile.link);
}
if(tile.block() instanceof BlockPart){
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){
stream.writeByte(Bits.packByte((byte)tile.getTeam().ordinal(), tile.getRotation()));
stream.writeShort((short)tile.entity.health); //health
if(tile.entity.items != null) tile.entity.items.write(stream);
if(tile.entity.power != null) tile.entity.power.write(stream);
if(tile.entity.liquids != null) tile.entity.liquids.write(stream);
if(tile.entity.items != null) tile.entity.items.write(stream);
if(tile.entity.power != null) tile.entity.power.write(stream);
if(tile.entity.liquids != null) tile.entity.liquids.write(stream);
tile.entity.write(stream);
}else if(tile.getWallID() == 0){
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);
for(int x = 0; x < width; x ++){
for(int y = 0; y < height; y ++){
byte floorid = stream.readByte();
byte blockid = stream.readByte();
byte elevation = stream.readByte();
for (int i = 0; i < width * height; i++) {
int x = i % width, y = i /width;
byte floorid = stream.readByte();
byte wallid = 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){
tile.link = stream.readByte();
byte team = Bits.getLeftByte(tr);
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) {
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;
i += consecutives;
}
tiles[x][y] = tile;
}
player.reset();

View File

@@ -108,7 +108,7 @@ public class Packets {
//player snapshot data
public float x, y, pointerX, pointerY, rotation, baseRotation, xv, yv;
public Tile mining;
public boolean boosting;
public boolean boosting, shooting;
@Override
public void write(ByteBuffer buffer) {
@@ -123,6 +123,7 @@ public class Packets {
buffer.putFloat(player.pointerX);
buffer.putFloat(player.pointerY);
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().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision));
@@ -144,6 +145,7 @@ public class Packets {
pointerX = buffer.getFloat();
pointerY = buffer.getFloat();
boosting = buffer.get() == 1;
shooting = buffer.get() == 1;
xv = buffer.get() / Unit.velocityPercision;
yv = buffer.get() / Unit.velocityPercision;
rotation = buffer.getShort()/2f;

View File

@@ -22,9 +22,7 @@ import io.anuke.ucore.util.Bundles;
import io.anuke.ucore.util.Log;
import io.anuke.ucore.util.Strings;
import static io.anuke.mindustry.Vars.maxNameLength;
import static io.anuke.mindustry.Vars.players;
import static io.anuke.mindustry.Vars.ui;
import static io.anuke.mindustry.Vars.*;
public class JoinDialog extends FloatingDialog {
Array<Server> servers = new Array<>();
@@ -275,6 +273,11 @@ public class JoinDialog extends FloatingDialog {
void connect(String ip, int port){
ui.loadfrag.show("$text.connecting");
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
netClient.disconnectQuietly();
});
Timers.runTask(2f, () -> {
try{
Vars.netClient.beginConnecting();

View File

@@ -135,7 +135,7 @@ public class SettingsMenuDialog extends SettingsDialog{
game.sliderPref("saveinterval", 90, 10, 5*120, i -> Bundles.format("setting.seconds", i));
if(!gwt){
graphics.checkPref("multithread", false, threads::setEnabled);
graphics.checkPref("multithread", true, threads::setEnabled);
if(Settings.getBool("multithread")){
threads.setEnabled(true);

View File

@@ -1,15 +1,18 @@
package io.anuke.mindustry.ui.fragments;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.ucore.function.Listenable;
import io.anuke.ucore.scene.Group;
import io.anuke.ucore.scene.builders.label;
import io.anuke.ucore.scene.builders.table;
import io.anuke.ucore.scene.event.Touchable;
import io.anuke.ucore.scene.ui.Label;
import io.anuke.ucore.scene.ui.TextButton;
import io.anuke.ucore.scene.ui.layout.Table;
public class LoadingFragment implements Fragment {
private Table table;
private TextButton button;
@Override
public void build(Group parent) {
@@ -25,11 +28,22 @@ public class LoadingFragment implements Fragment {
row();
get().addImage("white").growX()
.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();
table.setVisible(false);
}
public void setButton(Listenable listener){
button.setVisible(true);
button.getListeners().removeIndex(button.getListeners().size - 1);
button.clicked(listener);
}
public void show(){
show("$text.loading");
}
@@ -42,5 +56,6 @@ public class LoadingFragment implements Fragment {
public void hide(){
table.setVisible(false);
button.setVisible(false);
}
}

View File

@@ -28,8 +28,12 @@ public class Build {
/**Returns block type that was broken, or null if unsuccesful.*/
@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks)
public static void breakBlock(Player player, Team team, int x, int y){
if(Net.server() && !validBreak(team, x, y)){
return;
if(Net.server()){
if(!validBreak(team, x, y)){
return;
}
team = player.getTeam();
//throw new ValidateException(player, "An invalid block has been broken.");
}
@@ -43,7 +47,7 @@ public class Build {
Block previous = tile.block();
//remote players only
if(!player.isLocal){
if(player != null && !player.isLocal){
player.getPlaceQueue().clear();
player.getPlaceQueue().addFirst(new BuildRequest(x, y));
}
@@ -88,8 +92,12 @@ public class Build {
/**Places a BuildBlock at this location. Call validPlace first.*/
@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){
if(Net.server() && !validPlace(team, x, y, recipe.result, rotation)){
return;
if(Net.server()){
if(!validPlace(team, x, y, recipe.result, rotation)){
return;
}
team = player.getTeam();
//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.*/

View File

@@ -140,9 +140,12 @@ public class BreakBlock extends Block {
@Remote(called = Loc.server, in = In.blocks)
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);
}