From 7fb6b4cb9f7c1e6e2849e0b90598fae395b23beb Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 22 Jan 2018 17:07:07 -0500 Subject: [PATCH] Implemented and tested custom maps for multiplayer --- .../io/anuke/mindustry/core/NetClient.java | 32 +++++++--- .../io/anuke/mindustry/core/NetServer.java | 31 +++++++--- core/src/io/anuke/mindustry/io/Maps.java | 17 ++++++ .../src/io/anuke/mindustry/net/NetworkIO.java | 60 +++++++++++++++---- core/src/io/anuke/mindustry/net/Packets.java | 12 ++++ .../io/anuke/mindustry/net/Registrator.java | 2 + .../mindustry/ui/dialogs/PausedDialog.java | 12 +--- .../io/anuke/mindustry/world/ColorMapper.java | 20 ++++++- 8 files changed, 146 insertions(+), 40 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 792e8bbfbc..42efa894d5 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.utils.IntSet; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.Mindustry; @@ -14,9 +15,9 @@ import io.anuke.mindustry.entities.SyncEntity; import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.entities.enemies.EnemyType; import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net.SendMode; +import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.resource.Recipe; import io.anuke.mindustry.resource.Recipes; @@ -93,17 +94,22 @@ public class NetClient extends Module { Net.handle(WorldData.class, data -> { UCore.log("Recieved world data: " + data.stream.available() + " bytes."); - NetworkIO.load(data.stream); + NetworkIO.loadWorld(data.stream); Vars.player.set(Vars.control.core.worldx(), Vars.control.core.worldy() - Vars.tilesize * 2); - connecting = false; - Vars.ui.loadfrag.hide(); - Vars.ui.join.hide(); gotData = true; - Net.send(new ConnectConfirmPacket(), SendMode.tcp); - GameState.set(State.playing); - Net.setClientLoaded(true); + finishConnecting(); + }); + + Net.handle(CustomMapPacket.class, packet -> { + //custom map is always sent before world data + Pixmap pixmap = NetworkIO.loadMap(packet.stream); + + Vars.world.maps().setNetworkMap(pixmap); + + MapAckPacket ack = new MapAckPacket(); + Net.send(ack, SendMode.tcp); }); Net.handle(SyncPacket.class, packet -> { @@ -131,7 +137,6 @@ public class NetClient extends Module { } else { entity.read(data); } - } }); @@ -330,6 +335,15 @@ public class NetClient extends Module { } } + private void finishConnecting(){ + Net.send(new ConnectConfirmPacket(), SendMode.tcp); + GameState.set(State.playing); + Net.setClientLoaded(true); + connecting = false; + Vars.ui.loadfrag.hide(); + Vars.ui.join.hide(); + } + public void beginConnecting(){ connecting = true; } diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index ba038a08a0..6713e8fa84 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -62,19 +62,34 @@ public class NetServer extends Module{ player.interpolator.target.set(player.x, player.y); connections.put(id, player); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - NetworkIO.write(player.id, weapons.get(packet.name, new ByteArray()), stream); + if(Vars.world.getMap().custom){ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + NetworkIO.writeMap(Vars.world.getMap(), stream); + CustomMapPacket data = new CustomMapPacket(); + data.stream = new ByteArrayInputStream(stream.toByteArray()); + Net.sendStream(id, data); - UCore.log("Packed " + stream.size() + " uncompressed bytes of data."); - - WorldData data = new WorldData(); - data.stream = new ByteArrayInputStream(stream.toByteArray()); - - Net.sendStream(id, data); + UCore.log("Sending custom map: Packed " + stream.size() + " uncompressed bytes of MAP data."); + }else{ + //hack-- simulate the map ack packet recieved to send the world data to the client. + Net.handleServerReceived(id, new MapAckPacket()); + } Mindustry.platforms.updateRPC(); }); + Net.handleServer(MapAckPacket.class, (id, packet) -> { + Player player = connections.get(id); + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + NetworkIO.writeWorld(player.id, weapons.get(player.name, new ByteArray()), stream); + WorldData data = new WorldData(); + data.stream = new ByteArrayInputStream(stream.toByteArray()); + Net.sendStream(id, data); + + UCore.log("Packed " + stream.size() + " uncompressed bytes of WORLD data."); + }); + Net.handleServer(ConnectConfirmPacket.class, (id, packet) -> { Player player = connections.get(id); diff --git a/core/src/io/anuke/mindustry/io/Maps.java b/core/src/io/anuke/mindustry/io/Maps.java index 7acce3984e..bd4ced809c 100644 --- a/core/src/io/anuke/mindustry/io/Maps.java +++ b/core/src/io/anuke/mindustry/io/Maps.java @@ -17,6 +17,7 @@ import io.anuke.ucore.graphics.Pixmaps; public class Maps implements Disposable{ private IntMap maps = new IntMap<>(); private ObjectMap mapNames = new ObjectMap<>(); + private Map networkMap; private int lastID; private Json json = new Json(); @@ -30,7 +31,23 @@ public class Maps implements Disposable{ return maps.values(); } + public void setNetworkMap(Pixmap pixmap){ + if(networkMap != null){ + networkMap.pixmap.dispose(); + networkMap = null; + } + networkMap = new Map(); + networkMap.custom = true; + networkMap.pixmap = pixmap; + networkMap.visible = false; + networkMap.name = "network map"; + networkMap.id = -1; + } + public Map getMap(int id){ + if(id == -1){ + return networkMap; + } return maps.get(id); } diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index b22e8f2379..443b0266de 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -1,34 +1,74 @@ package io.anuke.mindustry.net; -import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.utils.ByteArray; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.Vars; import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.resource.Upgrade; import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.WorldGenerator; +import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.mindustry.world.blocks.types.BlockPart; import io.anuke.mindustry.world.blocks.types.Rock; +import io.anuke.ucore.UCore; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.Entities; import java.io.*; +import java.nio.ByteBuffer; public class NetworkIO { - public static void write(int playerID, ByteArray upgrades, OutputStream os){ + public static void writeMap(Map map, OutputStream os){ + try(DataOutputStream stream = new DataOutputStream(os)){ + Pixmap pix = map.pixmap; + ByteBuffer buffer = pix.getPixels(); + UCore.log("Buffer position: " + buffer.position()); + stream.writeShort(map.getWidth()); + stream.writeShort(map.getHeight()); + for(int i = 0; i < map.getWidth() * map.getHeight(); i ++){ + int color = buffer.getInt(); + byte id = ColorMapper.getColorID(color); + if(id == -1) id = 0; + stream.writeByte(id); + } + buffer.position(0); + }catch (IOException e){ + throw new RuntimeException(e); + } + } + + public static Pixmap loadMap(InputStream is){ + try(DataInputStream stream = new DataInputStream(is)){ + short width = stream.readShort(); + short height = stream.readShort(); + Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888); + ByteBuffer buffer = pixmap.getPixels(); + buffer.position(0); + + for(int i = 0; i < width * height; i ++){ + byte id = stream.readByte(); + buffer.putInt(ColorMapper.getColorByID(id)); + } + + return pixmap; + }catch (IOException e){ + throw new RuntimeException(e); + } + } + + public static void writeWorld(int playerID, ByteArray upgrades, OutputStream os){ try(DataOutputStream stream = new DataOutputStream(os)){ + stream.writeFloat(Timers.time()); //timer time stream.writeLong(TimeUtils.millis()); //timestamp //--GENERAL STATE-- stream.writeByte(Vars.control.getMode().ordinal()); //gamemode - stream.writeByte(Vars.world.getMap().id); //map ID + stream.writeByte(Vars.world.getMap().custom ? -1 : Vars.world.getMap().id); //map ID stream.writeInt(Vars.control.getWave()); //wave stream.writeFloat(Vars.control.getWaveCountdown()); //wave countdown @@ -139,12 +179,8 @@ public class NetworkIO { } } - public static void load(FileHandle file){ - load(file.read()); - } - - - public static void load(InputStream is){ + /**Return whether a custom map is expected, and thus whether the client should wait for additional data.*/ + public static void loadWorld(InputStream is){ try(DataInputStream stream = new DataInputStream(is)){ float timerTime = stream.readFloat(); diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 091a5b9659..af89163612 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -513,4 +513,16 @@ public class Packets { id = buffer.getInt(); } } + + public static class CustomMapPacket extends Streamable{ + + } + + public static class MapAckPacket implements Packet{ + @Override + public void write(ByteBuffer buffer) { } + + @Override + public void read(ByteBuffer buffer) { } + } } diff --git a/core/src/io/anuke/mindustry/net/Registrator.java b/core/src/io/anuke/mindustry/net/Registrator.java index 7bd4bfc351..3697588e7a 100644 --- a/core/src/io/anuke/mindustry/net/Registrator.java +++ b/core/src/io/anuke/mindustry/net/Registrator.java @@ -37,6 +37,8 @@ public class Registrator { GameOverPacket.class, FriendlyFireChangePacket.class, PlayerDeathPacket.class, + CustomMapPacket.class, + MapAckPacket.class }; private static ObjectIntMap> ids = new ObjectIntMap<>(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java index ab4bdb5e13..1fc9230359 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java @@ -57,11 +57,7 @@ public class PausedDialog extends FloatingDialog{ if(!Vars.gwt) { content().addButton("$text.hostserver", () -> { - if (Vars.world.getMap().custom) { - ui.showError("$text.nohost"); - } else { - ui.host.show(); - } + ui.host.show(); }).disabled(b -> Net.active()); } @@ -100,11 +96,7 @@ public class PausedDialog extends FloatingDialog{ lo.cell.disabled(b -> Net.active()); imagebutton ho = new imagebutton("icon-host", isize, () -> { - if(Vars.world.getMap().custom){ - ui.showError("$text.nohost"); - }else { - ui.host.show(); - } + ui.host.show(); }); ho.text("$text.host").padTop(4f); ho.cell.disabled(b -> Net.active()); diff --git a/core/src/io/anuke/mindustry/world/ColorMapper.java b/core/src/io/anuke/mindustry/world/ColorMapper.java index 4f8109b016..617c43bb95 100644 --- a/core/src/io/anuke/mindustry/world/ColorMapper.java +++ b/core/src/io/anuke/mindustry/world/ColorMapper.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.world; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntIntMap; import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.IntMap.Entry; import com.badlogic.gdx.utils.ObjectIntMap; @@ -10,6 +11,11 @@ import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.mindustry.world.blocks.SpecialBlocks; public class ColorMapper{ + /**maps color IDs to their actual RGBA8888 colors*/ + private static int[] colorIDS; + /**Maps RGBA8888 colors to pair IDs.*/ + private static IntIntMap reverseIDs = new IntIntMap(); + private static ObjectIntMap reverseColors = new ObjectIntMap<>(); private static Array pairs = new Array<>(); private static IntMap colors = map( @@ -40,6 +46,14 @@ public class ColorMapper{ public static BlockPair get(int color){ return colors.get(color); } + + public static int getColorByID(byte id){ + return colorIDS[id]; + } + + public static byte getColorID(int color){ + return (byte)reverseIDs.get(color, -1); + } public static IntMap getColors(){ return colors; @@ -62,10 +76,14 @@ public class ColorMapper{ } private static IntMap map(Object...objects){ + colorIDS = new int[objects.length/2]; IntMap colors = new IntMap<>(); for(int i = 0; i < objects.length/2; i ++){ - colors.put(Color.rgba8888(Color.valueOf((String)objects[i*2])), (BlockPair)objects[i*2+1]); + int color = Color.rgba8888(Color.valueOf((String)objects[i*2])); + colors.put(color, (BlockPair)objects[i*2+1]); pairs.add((BlockPair)objects[i*2+1]); + colorIDS[i] = color; + reverseIDs.put(color, i); } for(Entry e : colors.entries()){ reverseColors.put(e.value.wall == Blocks.air ? e.value.floor : e.value.wall, e.key);