diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index d6006d4bd3..089fd0dc49 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -275,7 +275,7 @@ public class Vars implements Loadable{ Core.settings.setDataDirectory(Core.files.local("saves/")); } - Core.settings.defaults("locale", "default"); + Core.settings.defaults("locale", "default", "blocksync", true); Core.keybinds.setDefaults(Binding.values()); Core.settings.load(); diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index a3b85facb0..2b2e1d4b76 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -342,6 +342,26 @@ public class NetClient implements ApplicationListener{ } } + @Remote(variants = Variant.both, priority = PacketPriority.low, unreliable = true) + public static void onBlockSnapshot(short amount, short dataLen, byte[] data){ + try{ + netClient.byteStream.setBytes(net.decompressSnapshot(data, dataLen)); + DataInputStream input = netClient.dataStream; + + for(int i = 0; i < amount; i++){ + int pos = input.readInt(); + Tile tile = world.tile(pos); + if(tile == null || tile.entity == null){ + Log.warn("Missing entity at {0}. Skipping block snapshot.", tile); + break; + } + tile.entity.read(input, tile.entity.version()); + } + }catch(Exception e){ + e.printStackTrace(); + } + } + @Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true) public static void onStateSnapshot(float waveTime, int wave, int enemies, short coreDataLen, byte[] coreData){ try{ diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 8de0e91fe4..8d42dcaed7 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -30,8 +30,8 @@ import java.util.zip.*; import static io.anuke.mindustry.Vars.*; public class NetServer implements ApplicationListener{ - public final static int maxSnapshotSize = 430; - private final static float serverSyncTime = 12, kickDuration = 30 * 1000; + private final static int maxSnapshotSize = 430, timerBlockSync = 0; + private final static float serverSyncTime = 12, kickDuration = 30 * 1000, blockSyncTime = 60 * 10; private final static Vector2 vector = new Vector2(); private final static Rectangle viewport = new Rectangle(); /** If a player goes away of their server-side coordinates by this distance, they get teleported back. */ @@ -41,6 +41,7 @@ public class NetServer implements ApplicationListener{ public final CommandHandler clientCommands = new CommandHandler("/"); private boolean closing = false; + private Interval timer = new Interval(); private ByteBuffer writeBuffer = ByteBuffer.allocate(127); private ByteBufferOutput outputBuffer = new ByteBufferOutput(writeBuffer); @@ -612,7 +613,35 @@ public class NetServer implements ApplicationListener{ } } - public void writeSnapshot(Player player) throws IOException{ + /** Sends a block snapshot to all players. */ + public void writeBlockSnapshots() throws IOException{ + syncStream.reset(); + + short sent = 0; + for(TileEntity entity : tileGroup.all()){ + if(!entity.block.sync) continue; + sent ++; + + dataStream.writeInt(entity.tile.pos()); + entity.write(dataStream); + + if(syncStream.size() > maxSnapshotSize){ + dataStream.close(); + byte[] stateBytes = syncStream.toByteArray(); + Call.onBlockSnapshot(sent, (short)stateBytes.length, net.compressSnapshot(stateBytes)); + sent = 0; + syncStream.reset(); + } + } + + if(sent > 0){ + dataStream.close(); + byte[] stateBytes = syncStream.toByteArray(); + Call.onBlockSnapshot(sent, (short)stateBytes.length, net.compressSnapshot(stateBytes)); + } + } + + public void writeEntitySnapshot(Player player) throws IOException{ syncStream.reset(); ObjectSet cores = state.teams.get(player.getTeam()).cores; @@ -726,7 +755,6 @@ public class NetServer implements ApplicationListener{ void sync(){ try{ - //iterate through each player for(int i = 0; i < playerGroup.size(); i++){ Player player = playerGroup.all().get(i); @@ -741,7 +769,11 @@ public class NetServer implements ApplicationListener{ if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue; - writeSnapshot(player); + writeEntitySnapshot(player); + } + + if(playerGroup.size() > 0 && Core.settings.getBool("blocksync") && timer.get(timerBlockSync, blockSyncTime)){ + writeBlockSnapshots(); } }catch(IOException e){ diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index e05f047b21..75f4fff536 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -94,6 +94,8 @@ public class Block extends BlockStorage{ public boolean drawLiquidLight = true; /** Whether the config is positional and needs to be shifted. */ public boolean posConfig; + /** Whether to periodically sync this block across the network.*/ + public boolean sync; /** Whether this block uses conveyor-type placement mode.*/ public boolean conveyorPlacement; /** diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java index 9f0f21f008..a7bfe3da97 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java @@ -18,6 +18,7 @@ public class PowerGenerator extends PowerDistributor{ public PowerGenerator(String name){ super(name); + sync = true; baseExplosiveness = 5f; flags = EnumSet.of(BlockFlag.producer); entityType = GeneratorEntity::new; diff --git a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 1878ada116..9f4c8c693d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -35,6 +35,7 @@ public class GenericCrafter extends Block{ hasItems = true; health = 60; idleSound = Sounds.machine; + sync = true; idleSoundVolume = 0.03f; entityType = GenericCrafterEntity::new; } diff --git a/gradle.properties b/gradle.properties index 1863b5958b..0e54b8fa9e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=58bfbdbe4446ca80d133a87cdffee249070ab32d +archash=2caecb328322b840a760dbb8877952867abd54d1 diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index acbe681ad9..f8f03f526e 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -527,6 +527,16 @@ public class ServerControl implements ApplicationListener{ info("Player &ly'{0}'&lg has been un-whitelisted.", info.lastName); }); + handler.register("sync", "[on/off...]", "Enable/disable block sync. Experimental.", arg -> { + if(arg.length == 0){ + info("Block sync is currently &lc{0}.", Core.settings.getBool("blocksync") ? "enabled" : "disabled"); + return; + } + boolean on = arg[0].equalsIgnoreCase("on"); + Core.settings.putSave("blocksync", on); + info("Block syncing is now &lc{0}.", on ? "on" : "off"); + }); + handler.register("crashreport", "", "Disables or enables automatic crash reporting", arg -> { boolean value = arg[0].equalsIgnoreCase("on"); Core.settings.put("crashreport", value);