Implemented and tested custom maps for multiplayer
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.anuke.ucore.graphics.Pixmaps;
|
||||
public class Maps implements Disposable{
|
||||
private IntMap<Map> maps = new IntMap<>();
|
||||
private ObjectMap<String, Map> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public class Registrator {
|
||||
GameOverPacket.class,
|
||||
FriendlyFireChangePacket.class,
|
||||
PlayerDeathPacket.class,
|
||||
CustomMapPacket.class,
|
||||
MapAckPacket.class
|
||||
};
|
||||
private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>();
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<Block> reverseColors = new ObjectIntMap<>();
|
||||
private static Array<BlockPair> pairs = new Array<>();
|
||||
private static IntMap<BlockPair> 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<BlockPair> getColors(){
|
||||
return colors;
|
||||
@@ -62,10 +76,14 @@ public class ColorMapper{
|
||||
}
|
||||
|
||||
private static IntMap<BlockPair> map(Object...objects){
|
||||
colorIDS = new int[objects.length/2];
|
||||
IntMap<BlockPair> 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<BlockPair> e : colors.entries()){
|
||||
reverseColors.put(e.value.wall == Blocks.air ? e.value.floor : e.value.wall, e.key);
|
||||
|
||||
Reference in New Issue
Block a user